{"owner":"petyosi","repo":"react-virtuoso","hasSkills":true,"totalSkillsCount":521,"totalTokensCount":578049,"categories":["marketplace","anthropic-skill","root-instruction","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".agents/plugins/marketplace.json",".claude-plugin/marketplace.json",".codex/skills/docs-guidance/SKILL.md",".codex/skills/docs-guidance/agents/openai.yaml",".codex/skills/docs-guidance/references/diagnostics.md",".codex/skills/docs-guidance/references/example-craft.md",".codex/skills/docs-guidance/references/page-shapes.md","AGENTS.md","CLAUDE.md","packages/data-table/CLAUDE.md","packages/data-table/README.md","packages/data-table/docs/1.installation/01.shadcn.md","packages/data-table/docs/1.installation/02.headless.md","packages/data-table/docs/2.data-model/01.local-data-model.md","packages/data-table/docs/2.data-model/02.remote-data-model.md","packages/data-table/docs/2.data-model/03.row-keys.md","packages/data-table/docs/2.data-model/index.md","packages/data-table/docs/3.columns/01.defining-columns.md","packages/data-table/docs/3.columns/02.cell-and-header-renderers.md","packages/data-table/docs/3.columns/03.column-groups.md","packages/data-table/docs/3.columns/04.sticky-columns.md","packages/data-table/docs/3.columns/05.column-visibility.md","packages/data-table/docs/3.columns/06.column-resizing.md","packages/data-table/docs/3.columns/07.column-reordering.md","packages/data-table/docs/3.columns/08.runtime-columns.md","packages/data-table/docs/3.columns/09.column-layout.md","packages/data-table/docs/3.columns/index.md","packages/data-table/docs/4.grouped-rows.md","packages/data-table/docs/5.state-persistence.md","packages/data-table/docs/6.controlling-the-table.md","packages/data-table/docs/7.customization/01.styling.md","packages/data-table/docs/7.customization/02.replacing-internals.md","packages/data-table/docs/7.customization/03.empty-and-loading-states.md","packages/data-table/docs/7.customization/04.scroll-containers.md","packages/data-table/docs/7.customization/05.ambient-context.md","packages/data-table/docs/7.customization/06.header-slots.md","packages/data-table/docs/7.customization/07.shadcn-wrapper.md","packages/data-table/docs/7.customization/08.card-mode.md","packages/data-table/docs/8.examples/01.basic-table.md","packages/data-table/docs/8.examples/03.grouped-data.md","packages/data-table/docs/8.examples/04.sticky-columns.md","packages/data-table/docs/8.examples/06.column-reorder.md","packages/data-table/docs/8.examples/07.local-filter-sort-group.md","packages/data-table/docs/8.examples/08.remote-offset.md","packages/data-table/docs/8.examples/09.remote-append.md","packages/data-table/docs/8.examples/10.table-instance-dashboard.md","packages/data-table/docs/8.examples/11.scrollbar-overlay.md","packages/data-table/docs/8.examples/12.custom-scroll-container.md","packages/data-table/docs/8.examples/13.column-resizing.md","packages/data-table/docs/8.examples/14.column-visibility.md","packages/data-table/docs/8.examples/15.state-persistence.md","packages/data-table/docs/8.examples/16.remote-column-sorting.md","packages/data-table/docs/9.guides/01.testing.md","packages/data-table/docs/9.guides/02.performance.md","packages/data-table/docs/9.guides/03.troubleshooting.md","packages/data-table/docs/9.guides/04.migrating-from-table-virtuoso.md","packages/data-table/docs/9.guides/05.debug-instrumentation.md","packages/data-table/src/features/README.md","packages/gurx/README.md","packages/masonry/README.md","packages/message-list/README.md","packages/message-list/docs/10.smooth-scrolling.md","packages/message-list/docs/11.testing.md","packages/message-list/docs/15.imperative-data-api.md","packages/message-list/docs/19.resize-observer-errors.md","packages/message-list/docs/2.tutorial/01.intro.md","packages/message-list/docs/2.tutorial/02.message-list.md","packages/message-list/docs/2.tutorial/03.loading-older-messages.md","packages/message-list/docs/2.tutorial/04.scroll-to-bottom-button.md","packages/message-list/docs/2.tutorial/05.receive-messages.md","packages/message-list/docs/2.tutorial/06.send-messages.md","packages/message-list/docs/2.tutorial/07.multiple-channels.md","packages/message-list/docs/20.scroll-modifier.md","packages/message-list/docs/21.controlled-mode-recipes.md","packages/message-list/docs/3.examples/01.messaging.md","packages/message-list/docs/3.examples/02.ai-chatbot.md","packages/message-list/docs/3.examples/03.gemini.md","packages/message-list/docs/3.examples/04.reactions.md","packages/message-list/docs/3.examples/05.date-separators.md","packages/message-list/docs/3.examples/06.scroll-to-reply.md","packages/message-list/docs/3.examples/07.grouped-messages.md","packages/message-list/docs/4.headers-footers.md","packages/message-list/docs/5.context.md","packages/message-list/docs/6.item-keys.md","packages/message-list/docs/7.hooks.md","packages/message-list/docs/8.custom-scrollbar.md","packages/message-list/docs/80.licensing.md","packages/message-list/docs/9.scrolling-to-item.md","packages/react-virtuoso/README.md","packages/react-virtuoso/docs/1.virtuoso/auto-resizing.md","packages/react-virtuoso/docs/1.virtuoso/basic-usage.md","packages/react-virtuoso/docs/1.virtuoso/custom-scroll-container.md","packages/react-virtuoso/docs/1.virtuoso/customize-rendering.md","packages/react-virtuoso/docs/1.virtuoso/endless-scrolling.md","packages/react-virtuoso/docs/1.virtuoso/footer.md","packages/react-virtuoso/docs/1.virtuoso/horizontal-mode.md","packages/react-virtuoso/docs/1.virtuoso/initial-index.md","packages/react-virtuoso/docs/1.virtuoso/keyboard-navigation.md","packages/react-virtuoso/docs/1.virtuoso/press-to-load-more.md","packages/react-virtuoso/docs/1.virtuoso/range-change-callback.md","packages/react-virtuoso/docs/1.virtuoso/scroll-handling.md","packages/react-virtuoso/docs/1.virtuoso/scroll-seek-placeholders.md","packages/react-virtuoso/docs/1.virtuoso/scroll-to-index.md","packages/react-virtuoso/docs/1.virtuoso/tailwind-test.md","packages/react-virtuoso/docs/1.virtuoso/top-items.md","packages/react-virtuoso/docs/1.virtuoso/window-scrolling.md","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-by-first-letter.md","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-numbers.md","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-with-load-on-demand.md","packages/react-virtuoso/docs/2.grouped-virtuoso/scroll-to-group.md","packages/react-virtuoso/docs/3.virtuoso-grid/grid-responsive-columns.md","packages/react-virtuoso/docs/4.table-virtuoso/basic-table.md","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-columns.md","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-headers.md","packages/react-virtuoso/docs/4.table-virtuoso/table-grouped.md","packages/react-virtuoso/docs/5.third-party-integration/material-ui-endless-scrolling.md","packages/react-virtuoso/docs/5.third-party-integration/mocking-in-tests.md","packages/react-virtuoso/docs/5.third-party-integration/mui-table-virtual-scroll.md","packages/react-virtuoso/docs/5.third-party-integration/tanstack-table-integration.md","packages/react-virtuoso/docs/6.troubleshooting.md","packages/reactive-engine-core/.claude/settings.local.json","packages/reactive-engine-core/README.md","packages/reactive-engine-core/docs/01.core-concepts.md","packages/reactive-engine-core/docs/02.engine-and-lifecycle.md","packages/reactive-engine-core/docs/03.transactions.md","packages/reactive-engine-core/docs/04.operators-and-combinators.md","packages/reactive-engine-core/docs/05.building-on-the-engine.md","packages/reactive-engine-core/docs/06.diagnostics.md","packages/reactive-engine-query/README.md","packages/reactive-engine-query/docs/01.queries-and-mutations.md","packages/reactive-engine-react/.claude/settings.local.json","packages/reactive-engine-react/README.md","packages/reactive-engine-react/docs/01.react-integration.md","packages/reactive-engine-router/README.md","packages/reactive-engine-router/docs/01.routing.md","packages/reactive-engine-storage/README.md","packages/reactive-engine-storage/docs/01.storage-links.md","packages/virtuoso-skills/.claude-plugin/plugin.json","packages/virtuoso-skills/CHANGELOG.md","packages/virtuoso-skills/README.md","packages/virtuoso-skills/package.json","packages/virtuoso-skills/skills/data-table/SKILL.md","packages/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","packages/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","packages/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","packages/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","packages/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","packages/virtuoso-skills/skills/data-table/references/2.data-model/index.md","packages/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","packages/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","packages/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","packages/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","packages/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","packages/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","packages/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","packages/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","packages/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","packages/virtuoso-skills/skills/data-table/references/3.columns/index.md","packages/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","packages/virtuoso-skills/skills/data-table/references/5.state-persistence.md","packages/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","packages/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","packages/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","packages/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","packages/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","packages/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","packages/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","packages/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","packages/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","packages/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","packages/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","packages/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","packages/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","packages/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","packages/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","packages/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","packages/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","packages/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","packages/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","packages/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","packages/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","packages/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","packages/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","packages/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","packages/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","packages/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","packages/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","packages/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","packages/virtuoso-skills/skills/data-table/references/README.md","packages/virtuoso-skills/skills/message-list/SKILL.md","packages/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","packages/virtuoso-skills/skills/message-list/references/11.testing.md","packages/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","packages/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","packages/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","packages/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","packages/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","packages/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","packages/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","packages/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","packages/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","packages/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","packages/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","packages/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","packages/virtuoso-skills/skills/message-list/references/4.headers-footers.md","packages/virtuoso-skills/skills/message-list/references/5.context.md","packages/virtuoso-skills/skills/message-list/references/6.item-keys.md","packages/virtuoso-skills/skills/message-list/references/7.hooks.md","packages/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","packages/virtuoso-skills/skills/message-list/references/80.licensing.md","packages/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","packages/virtuoso-skills/skills/message-list/references/README.md","packages/virtuoso-skills/skills/react-virtuoso/SKILL.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","packages/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","packages/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","packages/virtuoso-skills/skills/react-virtuoso/references/README.md","packages/virtuoso-skills/skills/reactive-engine/SKILL.md","packages/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","packages/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","packages/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","packages/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","packages/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","packages/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","packages/virtuoso-skills/skills/reactive-engine/references/core/README.md","packages/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","packages/virtuoso-skills/skills/reactive-engine/references/query/README.md","packages/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","packages/virtuoso-skills/skills/reactive-engine/references/react/README.md","packages/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","packages/virtuoso-skills/skills/reactive-engine/references/router/README.md","packages/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","packages/virtuoso-skills/skills/reactive-engine/references/storage/README.md","packages/virtuoso-skills/tsconfig.json","plugins/virtuoso-skills/.codex-plugin/plugin.json","plugins/virtuoso-skills/skills/data-table/SKILL.md","plugins/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","plugins/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","plugins/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","plugins/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","plugins/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","plugins/virtuoso-skills/skills/data-table/references/2.data-model/index.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","plugins/virtuoso-skills/skills/data-table/references/3.columns/index.md","plugins/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","plugins/virtuoso-skills/skills/data-table/references/5.state-persistence.md","plugins/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","plugins/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","plugins/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","plugins/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","plugins/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","plugins/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","plugins/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","plugins/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","plugins/virtuoso-skills/skills/data-table/references/README.md","plugins/virtuoso-skills/skills/message-list/SKILL.md","plugins/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","plugins/virtuoso-skills/skills/message-list/references/11.testing.md","plugins/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","plugins/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","plugins/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","plugins/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","plugins/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","plugins/virtuoso-skills/skills/message-list/references/4.headers-footers.md","plugins/virtuoso-skills/skills/message-list/references/5.context.md","plugins/virtuoso-skills/skills/message-list/references/6.item-keys.md","plugins/virtuoso-skills/skills/message-list/references/7.hooks.md","plugins/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","plugins/virtuoso-skills/skills/message-list/references/80.licensing.md","plugins/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","plugins/virtuoso-skills/skills/message-list/references/README.md","plugins/virtuoso-skills/skills/react-virtuoso/SKILL.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","plugins/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","plugins/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","plugins/virtuoso-skills/skills/react-virtuoso/references/README.md","plugins/virtuoso-skills/skills/reactive-engine/SKILL.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","plugins/virtuoso-skills/skills/reactive-engine/references/core/README.md","plugins/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","plugins/virtuoso-skills/skills/reactive-engine/references/query/README.md","plugins/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","plugins/virtuoso-skills/skills/reactive-engine/references/react/README.md","plugins/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","plugins/virtuoso-skills/skills/reactive-engine/references/router/README.md","plugins/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","plugins/virtuoso-skills/skills/reactive-engine/references/storage/README.md","skills/data-table/SKILL.md","skills/data-table/references/1.installation/01.shadcn.md","skills/data-table/references/1.installation/02.headless.md","skills/data-table/references/2.data-model/01.local-data-model.md","skills/data-table/references/2.data-model/02.remote-data-model.md","skills/data-table/references/2.data-model/03.row-keys.md","skills/data-table/references/2.data-model/index.md","skills/data-table/references/3.columns/01.defining-columns.md","skills/data-table/references/3.columns/02.cell-and-header-renderers.md","skills/data-table/references/3.columns/03.column-groups.md","skills/data-table/references/3.columns/04.sticky-columns.md","skills/data-table/references/3.columns/05.column-visibility.md","skills/data-table/references/3.columns/06.column-resizing.md","skills/data-table/references/3.columns/07.column-reordering.md","skills/data-table/references/3.columns/08.runtime-columns.md","skills/data-table/references/3.columns/09.column-layout.md","skills/data-table/references/3.columns/index.md","skills/data-table/references/4.grouped-rows.md","skills/data-table/references/5.state-persistence.md","skills/data-table/references/6.controlling-the-table.md","skills/data-table/references/7.customization/01.styling.md","skills/data-table/references/7.customization/02.replacing-internals.md","skills/data-table/references/7.customization/03.empty-and-loading-states.md","skills/data-table/references/7.customization/04.scroll-containers.md","skills/data-table/references/7.customization/05.ambient-context.md","skills/data-table/references/7.customization/06.header-slots.md","skills/data-table/references/7.customization/07.shadcn-wrapper.md","skills/data-table/references/7.customization/08.card-mode.md","skills/data-table/references/8.examples/01.basic-table.md","skills/data-table/references/8.examples/03.grouped-data.md","skills/data-table/references/8.examples/04.sticky-columns.md","skills/data-table/references/8.examples/06.column-reorder.md","skills/data-table/references/8.examples/07.local-filter-sort-group.md","skills/data-table/references/8.examples/08.remote-offset.md","skills/data-table/references/8.examples/09.remote-append.md","skills/data-table/references/8.examples/10.table-instance-dashboard.md","skills/data-table/references/8.examples/11.scrollbar-overlay.md","skills/data-table/references/8.examples/12.custom-scroll-container.md","skills/data-table/references/8.examples/13.column-resizing.md","skills/data-table/references/8.examples/14.column-visibility.md","skills/data-table/references/8.examples/15.state-persistence.md","skills/data-table/references/8.examples/16.remote-column-sorting.md","skills/data-table/references/9.guides/01.testing.md","skills/data-table/references/9.guides/02.performance.md","skills/data-table/references/9.guides/03.troubleshooting.md","skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","skills/data-table/references/9.guides/05.debug-instrumentation.md","skills/data-table/references/README.md","skills/message-list/SKILL.md","skills/message-list/references/10.smooth-scrolling.md","skills/message-list/references/11.testing.md","skills/message-list/references/15.imperative-data-api.md","skills/message-list/references/19.resize-observer-errors.md","skills/message-list/references/2.tutorial/01.intro.md","skills/message-list/references/2.tutorial/02.message-list.md","skills/message-list/references/2.tutorial/03.loading-older-messages.md","skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","skills/message-list/references/2.tutorial/05.receive-messages.md","skills/message-list/references/2.tutorial/06.send-messages.md","skills/message-list/references/2.tutorial/07.multiple-channels.md","skills/message-list/references/20.scroll-modifier.md","skills/message-list/references/21.controlled-mode-recipes.md","skills/message-list/references/3.examples/01.messaging.md","skills/message-list/references/3.examples/02.ai-chatbot.md","skills/message-list/references/3.examples/03.gemini.md","skills/message-list/references/3.examples/04.reactions.md","skills/message-list/references/3.examples/05.date-separators.md","skills/message-list/references/3.examples/06.scroll-to-reply.md","skills/message-list/references/3.examples/07.grouped-messages.md","skills/message-list/references/4.headers-footers.md","skills/message-list/references/5.context.md","skills/message-list/references/6.item-keys.md","skills/message-list/references/7.hooks.md","skills/message-list/references/8.custom-scrollbar.md","skills/message-list/references/80.licensing.md","skills/message-list/references/9.scrolling-to-item.md","skills/message-list/references/README.md","skills/react-virtuoso/SKILL.md","skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","skills/react-virtuoso/references/1.virtuoso/basic-usage.md","skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","skills/react-virtuoso/references/1.virtuoso/footer.md","skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","skills/react-virtuoso/references/1.virtuoso/initial-index.md","skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","skills/react-virtuoso/references/1.virtuoso/top-items.md","skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","skills/react-virtuoso/references/6.troubleshooting.md","skills/react-virtuoso/references/README.md","skills/reactive-engine/SKILL.md","skills/reactive-engine/references/core/01.core-concepts.md","skills/reactive-engine/references/core/02.engine-and-lifecycle.md","skills/reactive-engine/references/core/03.transactions.md","skills/reactive-engine/references/core/04.operators-and-combinators.md","skills/reactive-engine/references/core/05.building-on-the-engine.md","skills/reactive-engine/references/core/06.diagnostics.md","skills/reactive-engine/references/core/README.md","skills/reactive-engine/references/query/01.queries-and-mutations.md","skills/reactive-engine/references/query/README.md","skills/reactive-engine/references/react/01.react-integration.md","skills/reactive-engine/references/react/README.md","skills/reactive-engine/references/router/01.routing.md","skills/reactive-engine/references/router/README.md","skills/reactive-engine/references/storage/01.storage-links.md","skills/reactive-engine/references/storage/README.md"],"skills":{".agents/plugins/marketplace.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.agents%2Fplugins%2Fmarketplace.json -->",".claude-plugin/marketplace.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.claude-plugin%2Fmarketplace.json -->",".codex/skills/docs-guidance/SKILL.md":"---\nname: docs-guidance\ndescription: Revises project documentation for human readability, conceptual flow, and example safety. Covers concept pages, example pages, troubleshooting, installation, and migration docs. Use when writing or reviewing docs, when the user says a page \"feels off\", \"feels weird\", \"feels sudden\", or \"needs attention\", when asked to make a doc clearer or to rewrite a section, when fixing cold or abrupt openings, when a section opens with the API mechanism instead of the user's problem, or when comparing prose against a readability standard.\n---\n\n# Docs Guidance\n\nUse this skill for project documentation work. Treat docs quality as a product concern: the reader should understand where they are, why the topic exists, what shape the relevant data or API has, and when the shown pattern stops fitting.\n\nThis skill is intentionally general. Apply it to package docs, guides, examples, troubleshooting pages, migration notes, and installation pages across the repo.\n\n## Workflow\n\n1. Start from the reader's task, decision, or confusion, not from the API list.\n2. Identify the core contract the page must teach: state ownership, data shape, lifecycle, render boundary, persistence keying, configuration shape, backend shape, or control surface.\n3. Rewrite around the path: user problem → concept → contract → runnable example → limits and next step.\n4. Keep examples safe for real usage, not just short.\n5. End with a concrete escape hatch: where to go when this example no longer fits.\n\n## Core Readability Rules\n\nThese are the non-negotiables. Most doc-quality problems trace to violating one of them:\n\n- **Start from the reader's situation.** Open each page with the user problem, decision, or concrete setup. Never begin with an unexplained abstraction or API name unless the page is explicit API reference.\n- **Describe, don't dramatize.** State what the thing is and what it does in literal terms. Don't open with what users feel or expect (\"users expect…\", \"users develop opinions about…\"); don't give code or UI intentions, conflicts, or effort (\"the slot fights for space\", \"the header expects to do work\"); don't enumerate capabilities as atmospheric color when the page only covers some of them. Narrative motivation, anthropomorphism, and vibes lists all signal the writer didn't commit to a direct statement of the mechanism. See [diagnostics.md → Framing tone](references/diagnostics.md#framing-tone) for the specific smells.\n- **Define concepts before using them.** Don't introduce project terms (`engine`, `slot`, `pipeline`, `marker`, `adapter`, `state cell`, `registry`, `provider`) without grounding them in the user's mental model first.\n- **Explain why before how.** Show the pressure that creates the API — rows need stable identity, requests can be superseded, persisted state needs a stable key — before showing the call signature.\n- **For feature pages, lead with user-facing behavior.** If a feature has an end-user surface (something the user sees, clicks, drags, toggles), demonstrate that surface — visibly, interactively when it's interactive — before the developer-facing config or data contract. The developer is reading the page to enable an end-user behavior; the end-user behavior is the topic, not the config that supports it.\n- **Show the contract just before the recipe it grounds.** When you're about to drop a code block whose behavior depends on a contract, render the data/state/config shape one paragraph before the code so the example doesn't have to be reverse-engineered. This is a within-section pattern, not a whole-page ordering rule — at page level, the user-facing behavior (for feature pages) or the reader's problem (for concept pages) comes first.\n- **End with a concrete next step.** Every section should exit with a pointer to the next decision, deeper example, or escape hatch.\n\n## When to consult deeper material\n\nLoad these references when the work calls for them. Keeping them out of this file is deliberate — load only what the current task needs.\n\n- **[references/diagnostics.md](references/diagnostics.md)** — the invariants framework (what every page must teach) and the failure smells (what to look for when reviewing). Consult when reviewing, diagnosing, or auditing existing prose.\n- **[references/page-shapes.md](references/page-shapes.md)** — templates for concept, example, troubleshooting, installation, migration, and limitation-focused pages, plus templates for section-level shapes (gotcha-led, comparison, topic-first-alternative-second). Consult when writing a new page or restructuring an existing one.\n- **[references/example-craft.md](references/example-craft.md)** — rules for the code examples themselves: stable instances, variable-shape demo data, identifier conflict resolution, defensive-to-positive framing, and styling-layer choices. Consult when writing or auditing example code blocks.\n\n## Final Check\n\nBefore finishing, confirm:\n\n- Can a reader understand the first paragraph without reading another page?\n- Are all public terms introduced before use?\n- Does every code example model safe state ownership and lifecycle?\n- Does every advanced mechanism explain the user pressure that creates it?\n- Does the page tell the reader what to do next when this pattern is not enough?\n",".codex/skills/docs-guidance/agents/openai.yaml":"interface:\n  display_name: 'Docs Guidance'\n  short_description: 'Write readable project docs'\n  default_prompt: 'Use $docs-guidance to revise this documentation for human readability, conceptual flow, and safe examples.'\n",".codex/skills/docs-guidance/references/diagnostics.md":"# Diagnostics: Invariants and Smells\n\nTwo complementary frameworks. Invariants describe what every page must teach. Smells describe what to look for when something feels off.\n\nConsult this file when reviewing existing prose, auditing a page against a standard, or deciding which rule to apply to a vague \"this feels off\" comment.\n\n## Documentation Invariants\n\nWhen revising a page, look for the invariant that matters most and make it explicit. Do not let a page merely say an API exists — it should explain the invariant the API protects.\n\n- **Ownership.** Where state, configuration, data, or styling should live.\n- **Identity.** Which keys, names, IDs, fields, or handles must remain stable.\n- **Shape.** The exact shape of data, config, events, return values, or rendered structure.\n- **Lifecycle.** What is created once, reused, reset, persisted, fetched, cancelled, or cleaned up.\n- **Boundary.** Which package, wrapper, component, hook, adapter, or integration owns each responsibility.\n- **Failure mode.** What breaks when the pattern is used in the wrong context.\n- **Escape hatch.** Which API, guide, or example to use when the simple pattern no longer fits.\n\nA page that names its API but does not teach one of these invariants is incomplete. The invariant is the load-bearing knowledge; the API is just the surface for invoking it.\n\n## Readability Failure Smells\n\nRevise when a page has any of these. Grouped by where the smell shows up.\n\n### Opening and framing\n\n- Starts with a library abstraction before explaining the user problem.\n- Begins a section as if the reader already accepted an unstated premise.\n- Opens a section by recommending the alternative the section is meant to compare against. The section is supposed to teach _this_ approach — defer the alternative to a sidebar at the end.\n- Limitation-focused section opens with the recipe instead of the bug the recipe prevents. Lead with the trap, then the fix.\n\n### Concepts and terminology\n\n- Uses a term that only makes sense after reading another page.\n- Drifts into odd phrases, private shorthand, or terms the reader would not naturally use.\n- The same identifier appears in the same doc with different shapes (e.g., two helpers named `fieldsFromRows` returning different types). Rename one to disambiguate.\n\n### Section purpose\n\n- Describes mechanics but not the reason the mechanic exists.\n- Says what is possible but not when to choose it.\n- Explains internal architecture when the user needs a decision rule.\n- The section's audience overlaps poorly with the parent page's audience — it likely belongs in a different page.\n- Has paragraphs that restate the previous paragraph's claim (\"This means…\", \"This keeps things X…\", \"In other words…\"). Meta-commentary that adds no new information; drop it.\n\n### Structure and navigation\n\n- Headings accurate but not helpful to a scanning reader. A heading should announce what the reader will learn, not just name the API.\n- Jumps from install or setup straight into advanced options without a \"what now\" bridge.\n- Cross-link text is vague (\"see X\"). The link text should answer \"why follow this.\" Prefer `[Generated columns](…) for type-aware columns` over `see [Generated columns](…)`.\n\n### Example quality\n\n- Presents \"correct\" code that would be unsafe in real usage.\n- Demonstrates a feature for dynamic or variable data using uniform example rows — silently hiding the variability question the feature exists to answer.\n\n### Feature pages\n\n- Page teaches the data contract or config object thoroughly but never shows what the end user can do with the feature. The reader closes the page knowing the shape but unable to picture the result or the interaction.\n- An interactive feature (the user clicks, drags, toggles, regroups, reorders) is demonstrated only as a static example. No control to click, no model action paired with UI. If the feature is interactive, the example must show the interaction.\n- Page leads with the data shape rather than what the end user sees. Almost always a sign that the writer let the cleanest-to-explain thing become the lead. The fix is to invert: end-user behavior first, contract last.\n- Interactive variations (Group By Category, Sort by Name) appear only as a bullet in a list of options, never as a worked example with the UI surface that fires them.\n\n### Framing tone\n\n- Uses defensive framing for things the library deliberately leaves to the user. (\"The package does not ship X\" → \"X lives in your code so the rules stay obvious next to the table.\")\n- Apologetic phrasing for intentional design choices (\"currently does not re-fetch\") instead of confident phrasing (\"intentionally one-shot\").\n- **Narrative motivation in openers.** Opens with what users want, feel, or expect instead of what the page documents. _\"Users expect headers to do work\"_, _\"A column header is rarely just a label\"_, _\"Wide tables push key context off-screen the moment a reader scrolls\"_ — these are scene-setting. The reader is here for the mechanism; preface drama is friction. Replace with a direct statement of the structure: _\"A column header has four slot positions for additional UI: …\"_.\n- **Anthropomorphism of inanimate code.** Gives UI parts intentions, conflicts, or effort. _\"fighting the label for space\"_, _\"the slots compete\"_, _\"the header expects\"_, _\"the column wants to be wider\"_. Replace with literal mechanics — _\"the slot is positioned outside the label's flex flow\"_ beats _\"the slot doesn't fight the label for space\"_. If the literal mechanic is unclear, the metaphor was hiding that the writer didn't know.\n- **Vibes enumeration.** Lists capabilities as atmospheric color rather than as commitments to coverage. _\"sort the column, open a filter menu, expose a resize handle, surface a 'Pin to left' affordance\"_ — reads like marketing. Either commit to which the page covers (and link out for the rest), or replace the list with the structural fact behind it (_\"Any UI that mounts into one of these four slots\"_). A list of nice-sounding capabilities the page won't actually teach is filler.\n- **\"Rarely just X\" / \"More than just X\" framings.** Opening with a denial of the obvious in order to motivate the topic. _\"A column header is rarely just a label\"_, _\"This isn't just a placeholder\"_, _\"Sticky columns are more than a CSS trick\"_. The reader didn't claim the thing was just X; the denial creates a fake premise the page then dismantles. Drop the denial and state what the thing is.\n\n## Triage rule\n\nIf a page exhibits multiple smells, fix the opening first. A confused opening cascades: every section after it is read through the wrong lens, so smells later in the page often dissolve once the lead is right.\n",".codex/skills/docs-guidance/references/example-craft.md":"# Example Craft\n\nRules for the code examples themselves — how to construct them so they teach honestly without hiding the lifecycle, state ownership, or variability that real usage will hit.\n\nConsult this file when writing or auditing the code blocks inside a doc page (live demos, recipe snippets, illustrative JSX).\n\n## Production-Safe Defaults\n\nExamples should model the safe version, not a shortened one that hides the real constraints.\n\n- Use stable instances for stateful objects (no inline `new X()` in render).\n- Use stable identities (`key`, `id`) when items can move, persist, or be restored.\n- Include required cleanup, cancellation, or abort handling when async work is shown.\n- Keep generated data schemas deterministic.\n- Keep persistence examples clear about what is and is not saved.\n- Keep styling examples attached to the right layer: local content styling, wrapper styling, component overrides, or global CSS.\n- Avoid examples that are only short because they hide the lifecycle or state-ownership rule.\n\n## Demo Data Craftsmanship\n\nThe shape of the data matters as much as the code that processes it.\n\n- **Variable-shape data for dynamic-feature examples.** When demonstrating something that handles schema variability, the example rows must actually vary — some rows with extra fields, some without. Uniform example rows silently hide the question the feature exists to answer.\n- **Realistic field counts.** Three uniform columns make every feature look easy; six fields with mixed types stress the layout the same way real usage will.\n- **Schemas the reader recognises.** Use product/order/user shapes the reader has seen before. Resist the urge to invent fictional domains that need their own explanation.\n\n## Identifier Discipline Within a Doc\n\n- **Resolve identifier conflicts.** When you introduce a function, type, or prop name, scan the rest of the page for collisions. Two helpers named `fieldsFromRows` with different signatures in the same doc is a real confusion source. Rename one to disambiguate (`fieldsToShow`, `runtimeFields`, etc.).\n- **Inline helpers vs. canonical helpers.** When an example uses a slim inline helper because the canonical full helper would be overkill, name it differently so the reader can tell which one they're looking at.\n\n## Framing Tone\n\nThe words around the code carry the design's intent. Defensive or apologetic phrasing makes intentional choices look like missing features.\n\n- **Defensive to positive.** When the library deliberately doesn't ship a helper, reframe the absence as a benefit: _\"Field scanning lives in your app code so the rules stay obvious next to the table\"_ beats _\"The package does not inspect your row shape for you\"_.\n- **No apologies for design choices.** If the one-shot nature of a feature is intentional, say \"intentionally one-shot\" — not \"currently does not re-fetch\" (which sounds like a missing feature).\n- **Acknowledge limitations honestly.** When a constraint really is a limitation, name it as one and point at the workaround: _\"Offset placeholders cannot be distinguished from real rows. Use schema-compatible placeholders or backend-provided column metadata.\"_ Honest framing beats both apology and silence.\n\n## Component-Example Style\n\nPatterns that recur in component-library docs.\n\n- **`className` over wrapping `<div>`.** When a component accepts a `className`, prefer it to wrapping the render result in a styled `<div>`. Wrapping changes the DOM structure and breaks layout assumptions in many table, grid, and list contexts; using `className` keeps the wrapping consistent with non-example usage.\n- **Use the wrapper component the project ships.** If the project ships a shadcn-style wrapper (`DataTableCell`, `DataTableColumn`), use it in examples instead of the unwrapped primitive. The wrapped form is what users will copy; show that path.\n- **Skip the JSX import boilerplate in snippets.** Inline code blocks in the middle of prose are clearer when they show only the JSX under discussion. Save the full `import` list for live blocks that need to run.\n",".codex/skills/docs-guidance/references/page-shapes.md":"# Page Shapes and Section Shapes\n\nTemplates for whole pages (by type) and for recurring section shapes within pages.\n\nConsult this file when writing a new page from scratch, restructuring an existing page, or naming/ordering the sections of a page that already has its content roughed in.\n\n## Page Shapes by Type\n\n### Concept pages\n\nFor pages that introduce a single concept or primitive (a model, a hook, a low-level component). The audience is developers learning the primitive itself.\n\nIf the page describes a feature with a user-facing surface — grouping, filtering, drag-and-drop, sticky pinning, column reorder, visibility toggles — use the **Feature pages** template instead. Concept template is for pure-developer-facing primitives.\n\n- Open with the reader problem.\n- Explain the concept in ordinary words.\n- Show the data/state/API contract.\n- Show the smallest safe example.\n- Add runtime/lifecycle caveats.\n- Link to one deeper example or adjacent decision.\n\n### Feature pages\n\nFor pages that describe a user-facing capability — grouped rows, filtering, sorting, column reorder, column visibility, sticky pinning, drag-and-drop, expand/collapse. The developer reading the page is configuring something their end user will see or interact with.\n\nThe temptation in feature docs is to lead with the data shape or config object because it's the cleanest thing the writer understands. Resist it — the data shape is what makes the feature work, not what makes the feature interesting to the reader.\n\n- **What the end user sees.** Open with a sentence (or a small \"this is the result\" demo) describing the visible outcome.\n- **What the end user does.** If the feature is interactive, demonstrate the interaction with a live example that includes the UI control the user clicks, toggles, or drags. Static-only demos of interactive features are a smell — see [diagnostics.md](diagnostics.md#feature-pages).\n- **How to wire the interaction.** The API surface that exposes the behavior (model action, hook, prop) paired with the UI piece that triggers it. Treat the UI and the model wiring as one recipe; splitting them hides the half the reader came for.\n- **The data contract or config shape.** Only after the interaction has been shown. The contract is in service of the recipe above it.\n- **Variations.** Local vs remote, simple vs interactive, single-level vs multi-level — each as a sub-section that re-uses the user-behavior-first ordering.\n- **Limits and escape hatches.** Close with what doesn't fit and where to go next.\n\n### Example pages\n\nFor pages that demonstrate a recipe end-to-end.\n\n- First paragraph: \"This example uses X to solve Y.\"\n- `## APIs used` — short list grouped by job, not exhaustive reference.\n- Runnable example code.\n- Closing section named `## Notes`, `## When this doesn't fit`, `## When to outgrow this shape`, or `## What this leaves out`.\n\n### Troubleshooting and performance pages\n\n- Lead with symptoms and decision rules.\n- Prefer tables mapping symptom → fix.\n- Avoid implying users should tune defaults without a concrete symptom.\n\n### Installation pages\n\n- State what gets installed or generated.\n- Distinguish local editable code from package dependencies.\n- Show the smallest successful setup.\n- Link to the next required setup tasks.\n\n### Migration pages\n\n- State what is not a drop-in replacement.\n- Map old concepts to new concepts.\n- Give a recommended sequence, not a pile of independent facts.\n- End with the core mental-model shift.\n\n## Section Shapes (within pages)\n\nThese shapes recur across multiple page types — use them wherever they fit.\n\n### Limitation-focused section (gotcha-led)\n\nWhen a section exists because of a trap — \"Placeholders\" in a runtime-discovery page, \"Empty responses\" in a remote-data page, \"Schema drift\" in a persistence page:\n\n1. Open with the trap. Describe the bug the section's recipe prevents.\n2. Show the recipe.\n3. Add sidebar advice for cases the recipe still doesn't cover.\n\nExample opening: _\"The field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data.\"_ — then the schema-compatible placeholder recipe.\n\n### Comparison / \"When this does not fit\" section\n\nWhen enumerating alternatives at the end of a page, use **named bullets** with bold term labels:\n\n```markdown\n- **Backend-defined schemas.** Use when the backend already knows the shape.\n- **Statically-known schemas.** Use when the schema is in code but the JSX would be repetitive.\n- **Runtime column-management UI.** Lift the field list into your own state when the user can add or reorder fields after mount.\n```\n\nClose with a one-sentence summary of when the current page's approach _does_ fit.\n\n### Topic-first, alternative-second\n\nNever open a section by recommending the alternative it is meant to compare against. Pattern:\n\n1. Show the section's actual topic.\n2. After the example, add a sidebar paragraph for the alternative — \"When the backend can return column metadata alongside the rows, prefer that.\"\n\n### Parameter / config explanation block\n\nWhen showing what arrives at a render prop, hook, or callback, use bullets per parameter:\n\n```markdown\nThe render function is called with `{ data, model }`:\n\n- `data` is X — it does not change when Y.\n- `model` is Z — available for the rare case where W.\n```\n\nEasier to scan than parallel prose sentences.\n\n### \"How the contract looks\" before the example\n\nWhen behavior depends on a contract (data shape, config shape, event payload), render the shape inline before the runnable example:\n\n````markdown\nThe render function receives:\n\n```ts\n{\n  data: readonly Row[]\n  model: DataModelHandle<Row>\n}\n```\n\nThen the example…\n````\n\nThe shape grounds the example. Without it, the reader has to reverse-engineer the contract from the example body.\n","AGENTS.md":"# AGENTS.md\n\nRepo-specific guidance for agents working in `react-virtuoso`.\n\nUse this file for repo-specific navigation, validation, and documentation rules only. Keep it short, concrete, and limited to information that helps an agent make better decisions in this repository.\n\nIf a rule here appears to conflict with `package.json`, the actual filesystem, or a more specific instruction file in scope, trust the repo state and the more specific instruction.\n\n## High-Signal Rules\n\n- This is a `pnpm` workspaces monorepo. Prefer running commands from the repo root unless you need package-local scope.\n- Root `pnpm lint` does **not** run type checking. Run `pnpm typecheck` separately when you need type validation.\n- Internal working artifacts belong in Notion, not in repo markdown files.\n  - Use Notion for PRPs, plans, clarifications, execution notes, verification notes, prompts, investigations, reports, audits, and exported findings that are not product docs.\n  - Do not create or keep repo markdown artifacts in folders like `plans/`, `prompts/`, `reports/`, or similar ad hoc locations.\n  - Create new internal artifacts from the shared Notion template at `https://www.notion.so/3461834d8eff81318a4cccaf19b0de51` instead of creating free-form pages from scratch.\n  - The template's visual settings such as `Small text` and `Full width` are maintained manually in Notion. Preserve them by duplicating the template when possible.\n  - If the user asks for a PRP workflow, create or update the artifact in Notion instead of creating `plans/*.md`.\n  - If the user asks for a report or findings export, prefer a Notion page unless they explicitly ask for a repo file.\n  - Set a page icon that matches the artifact type or content when creating the page. Prefer clear, stable icons such as `📗` for PRPs/plans, `📊` for reports, `🔎` for investigations, `✍️` for prompts, and `🛠️` for execution notes.\n  - If an existing markdown file looks ambiguous, ask before deleting or migrating it.\n- Never use `data-testid` as a styling hook. It is reserved for tests. Use semantic attributes such as `data-table-element-role` instead.\n\n## NPM Registry Queries\n\nThis repo uses `devEngines.packageManager` with `\"onFail\": \"error\"` in the root `package.json`. `pnpm info` and `pnpm view` can fail because they delegate to npm and npm 11 rejects the request in this setup.\n\nUse npm from outside the repo instead:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Workspace Layout\n\n### Main packages\n\n- `packages/data-table` - virtualized data table package; currently the most active package in this repo\n- `packages/react-virtuoso` - main virtualization library\n- `packages/message-list` - chat/message list package\n- `packages/masonry` - masonry layout package\n- `packages/gurx` - reactive state library used by older Virtuoso internals\n- `packages/reactive-engine-*` - lower-level engine packages used by `data-table`\n\n### Apps and examples\n\n- `apps/virtuoso.dev` - Astro/Starlight docs site\n- `examples` - separate workspace for shared Ladle/integration examples\n- `packages/react-virtuoso/examples` - example pages used by `packages/react-virtuoso/e2e`\n\n## Plugin Distribution\n\n- This repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n- After merge, public Codex plugin install is:\n\n  ```bash\n  codex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\n  codex plugin add virtuoso-skills@virtuoso\n  ```\n\n- Source skill files live at `packages/virtuoso-skills/skills/<name>/SKILL.md`.\n- Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly. They are generated by `pnpm build:skills`.\n- Do not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Commands\n\n### Root\n\n- `pnpm build` - build all workspaces\n- `pnpm build:skills` - regenerate skill references and public skill mirrors\n- `pnpm validate:skills` - validate Claude and Codex plugin manifests\n- `pnpm typecheck` - run type checking across workspaces\n- `pnpm lint` - run package lint scripts across workspaces\n- `pnpm format` - format with `oxfmt`\n- `pnpm format:check` - check formatting with `oxfmt`\n- `pnpm test` - run workspace test scripts\n- `pnpm e2e` - run workspace e2e scripts\n- `pnpm lint:md` / `pnpm lint:md:fix` - markdown lint / fix\n- `pnpm ci` - full CI sequence: setup, build, typecheck, lint, markdown lint, test, e2e\n- `pnpm dev:docs` - start the docs site\n- `pnpm changeset-add` - add a changeset\n\n### `packages/data-table`\n\n- `pnpm build` - `tsc && vite build`\n- `pnpm typecheck` - `tsgo -b --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - `vitest run --browser.headless`\n- `pnpm check` - `format:check + lint + typecheck`\n- `pnpm dev` - run Ladle serve mode\n- `pnpm dev:build` / `pnpm dev:preview` - Ladle build / preview\n\n### `packages/react-virtuoso`\n\n- `pnpm build` - Vite build\n- `pnpm typecheck` - `tsgo --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - Vitest\n- `pnpm test:watch` - Vitest watch\n- `pnpm e2e` - Playwright\n- `pnpm ladle` - preview examples in Ladle\n\n### `apps/virtuoso.dev`\n\n- `pnpm dev` - Astro dev server\n- `pnpm build` - `shadcn build && astro build`\n- `pnpm lint` - `oxlint --type-aware --type-check && astro check`\n- `pnpm format` / `pnpm format:check` - `oxfmt` plus Prettier for `.astro`\n\n## Documentation Workflow\n\nThe docs site contains generated package docs. Do not edit the generated content under:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nEdit the source docs in the package instead:\n\n- `packages/data-table/README.md` and `packages/data-table/docs/*.md`\n- `packages/react-virtuoso/README.md` and `packages/react-virtuoso/docs/*.md`\n- `packages/message-list/README.md` and `packages/message-list/docs/*.md`\n- `packages/masonry/README.md`\n- `packages/gurx/README.md`\n\nAfter product docs changes:\n\n- run `pnpm lint:md` from the repo root\n- if docs-site content or registry code changed, run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n\n## Validation Guidance\n\nChoose the narrowest validation that matches the change:\n\n- `packages/data-table` code changes: start with `pnpm check && pnpm test` in `packages/data-table`\n- `packages/react-virtuoso` code changes: run `pnpm lint && pnpm typecheck && pnpm test`, add `pnpm e2e` when behavior changes\n- docs-only markdown changes: run `pnpm lint:md`\n- docs site or registry changes: run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n- broad cross-package changes: run root `pnpm typecheck && pnpm lint && pnpm test`\n\n## Architecture Notes\n\n- `packages/react-virtuoso` uses the custom `urx` stream/state system under `packages/react-virtuoso/src/urx`. If you touch its internals, follow existing `*System.ts` and `react-urx/` patterns.\n- `packages/data-table` is a separate package built on the `reactive-engine-*` workspaces. When working there, prefer matching existing engine/cell/stream patterns instead of borrowing directly from `react-virtuoso` internals.\n\n## Git Hooks\n\nThis repo uses `lefthook`. Pre-commit hooks format and lint staged files. If you need to skip them for a WIP commit:\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: ...\"\n```\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## NPM Registry Queries\n\nThis project uses `devEngines.packageManager` with `\"onFail\": \"error\"` in `package.json`, which causes `pnpm info` and `pnpm view` to fail because they delegate to npm, and npm 11 rejects the request. To check package versions, run npm from outside the project directory:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Build Commands\n\nThis is a pnpm workspaces monorepo. Run commands from the root or within specific workspace packages.\n\n### Root-level commands\n\nRun from repository root for all packages:\n\n- Build all: `pnpm build`\n- Lint all (includes type checking): `pnpm lint`\n- Format all: `pnpm format` (oxfmt)\n- Format check: `pnpm format:check`\n- Test all: `pnpm test`\n- E2E tests all: `pnpm e2e`\n- Markdown lint: `pnpm lint:md` / fix: `pnpm lint:md:fix`\n- Full CI: `pnpm ci` (setup, build, lint, lint:md, test, e2e)\n- Release: `pnpm release` (build + publish with changesets)\n- Add changeset: `pnpm changeset-add`\n- Dev docs site: `pnpm dev:docs`\n\n### react-virtuoso package (packages/react-virtuoso/)\n\n- Build: `pnpm run build` (uses vite)\n- Test: `pnpm run test` (vitest)\n- Test watch: `pnpm run test:watch`\n- Run single test: `pnpm vitest <test-file-path>` or `pnpm vitest -t \"<test-name>\"`\n- E2E tests: `pnpm run e2e` (playwright)\n- Lint (includes type checking): `pnpm run lint`\n- Format: `pnpm run format` (oxfmt)\n- Format check: `pnpm run format:check`\n- Dev/preview examples: `pnpm run ladle` (launches Ladle server for browsing examples/ folder)\n\n### virtuoso.dev docs app (apps/virtuoso.dev/)\n\n- Dev server: `pnpm run dev`\n- Build: `pnpm run build`\n- Format: `pnpm run format` (oxfmt + Prettier for .astro files)\n\nAfter docs changes: run `pnpm lint` from the app directory or root.\n\n**IMPORTANT - Documentation Locations:**\n\n**DO NOT EDIT** the following auto-generated directories:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nThese are auto-synced from source files + TypeDoc API via the `docsSync` integration. Any edits will be overwritten.\n\n**To edit package documentation**, modify the source files in each package:\n\n- **data-table**: `packages/data-table/docs/*.md`\n- **react-virtuoso**: `packages/react-virtuoso/README.md` or `packages/react-virtuoso/docs/*.md`\n- **masonry**: `packages/masonry/README.md` or `packages/masonry/docs/*.md`\n- **gurx**: `packages/gurx/README.md` or `packages/gurx/docs/*.md`\n- **message-list**: `packages/message-list/README.md` or `packages/message-list/docs/*.md`\n\n## Plugin Distribution\n\nThis repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n\nEdit source skill content in `packages/virtuoso-skills/skills/<name>/SKILL.md`. Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly; regenerate them with:\n\n```bash\npnpm build:skills\n```\n\nAfter merge, public Codex plugin install uses:\n\n```bash\ncodex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\ncodex plugin add virtuoso-skills@virtuoso\n```\n\nDo not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Monorepo Structure\n\n```text\npackages/\n  react-virtuoso/    - Main virtualization library\n  gurx/             - urx state management (fork/variant)\n  masonry/          - Masonry layout component\n  message-list/     - Chat/message list component\n  tooling/          - Shared build tooling\n\napps/\n  virtuoso.dev/     - Starlight/Astro documentation site\n\nexamples/            - Ladle stories for testing/development\n```\n\n## Architecture\n\n### State Management: urx System\n\nThe codebase uses **urx**, a custom reactive state management system built on streams/observables. Core concepts:\n\n- **Systems**: Stateful data-processing machines composed of streams\n- **Streams**: Can be stateless (signals) or stateful (depots that persist values)\n- **Depots**: Implicit state maintained in stateful streams, transformers (combineLatest), or operators (withLatestFrom, scan)\n- **Input/Output**: Systems receive input via input streams, process via transformers/operators, emit via output streams\n\nKey urx files: `src/urx/` directory contains:\n\n- `system.ts` - System creation and composition\n- `streams.ts` - Stream primitives\n- `pipe.ts` - Stream operators and transformers\n- `actions.ts` - Publishing/emitting\n- `transformers.ts` - Stream transformation utilities\n\n### Component Architecture\n\nThe virtualization logic is split into modular systems in `src/`:\n\n**Core Systems:**\n\n- `listSystem.ts` - Composes all feature systems into the main list system\n- `sizeSystem.ts` - Tracks and manages item sizes (critical for variable-height items)\n- `listStateSystem.ts` - Manages visible item ranges and scrolling state\n- `domIOSystem.ts` - DOM measurements and interactions\n\n**Feature Systems:**\n\n- `groupedListSystem.ts` - Grouped lists with sticky headers\n- `scrollToIndexSystem.ts` - Programmatic scroll positioning\n- `followOutputSystem.ts` - Auto-scroll for chat/feed UIs\n- `initialTopMostItemIndexSystem.ts` - Initial scroll position\n- `scrollSeekSystem.ts` - Placeholder rendering during fast scrolling\n- `windowScrollerSystem.ts` - Window-scrolling mode\n- And many more in `src/*System.ts` files\n\n**React Integration:**\n\n- `react-urx/` - Bridges urx systems to React components\n- `Virtuoso.tsx` - Main list component\n- `VirtuosoGrid.tsx` - Grid layout component\n- `TableVirtuoso.tsx` - Table virtualization component\n- Component interfaces in `component-interfaces/`\n\n### Size Calculation\n\nVariable-sized items work automatically via `sizeSystem.ts`:\n\n- Uses ResizeObserver for measurements\n- Maintains size ranges and estimates\n- No manual height specification needed\n- `correctItemSize()` utility in `utils/` handles size corrections\n\n### E2E Testing\n\nE2E tests in `packages/react-virtuoso/e2e/`:\n\n- Test files: `*.test.ts` (Playwright tests)\n- Example pages: `examples/*.tsx` (rendered in browser for tests)\n- Use Ladle (`pnpm run ladle`) to preview examples during development\n\n## Code Style\n\n- Never use `data-testid` attributes for CSS styling selectors. `data-testid` is reserved for testing only. Use `data-table-element-role` or other semantic data attributes for styling hooks.\n- TypeScript with strong typing; avoid `any`\n- oxfmt: 140 char width, single quotes, no semicolons\n- Naming: camelCase for variables/functions, PascalCase for components\n- Imports: React first, external libs, then internal modules\n- Functional components with hooks preferred\n- Use urx system patterns for state management\n- Error handling: prefer early returns\n\n## Markdown Style Guide\n\nWhen writing or editing markdown documentation:\n\n### Formatting Rules\n\n- Headings: ATX-style (`# Heading` not `Heading\\n=======`)\n- Code blocks: Always use fenced blocks with language specifiers\n\n```typescript\nconst foo = 'bar'\n```\n\n- Lists: Use `-` for unordered lists, indent nested items by 2 spaces\n- Emphasis: Use `_single underscore_` for emphasis, `**double asterisk**` for strong\n- Links: Prefer inline links `[text](url)` for readability\n\n### Content Guidelines\n\n- Start with clear, descriptive headings (H1 for title, H2 for major sections)\n- Use code blocks for all code examples, terminal commands, and file paths\n- Include language identifiers in fenced code blocks (`typescript`, `bash`, `json`)\n- Break long paragraphs into shorter ones (3-5 sentences max)\n- Use tables for structured data comparison\n- Add blank lines before and after headings, lists, code blocks, and tables\n\n### Special Cases\n\n- Inline HTML allowed for badges, complex layouts, or special formatting\n- Bare URLs allowed in reference sections and changelogs\n- Line length not enforced (practical for existing docs)\n- Multiple H1 headings allowed (document sections)\n\n### Linting\n\n- Run `pnpm lint:md` to check markdown files\n- Run `pnpm lint:md:fix` to auto-fix issues\n- Pre-commit hooks automatically lint staged .md files\n- Configuration: `.markdownlint.json` and `.markdownlintignore`\n- If necessary, use `markdownlint` CLI directly, but prefer pnpm scripts\n\n## Code Change Checklist\n\nAfter making code changes, run these commands to verify quality:\n\n### Required (always run)\n\n- `pnpm lint` - Lint and type check (oxlint --type-aware --type-check)\n- `pnpm format` - Format code with oxfmt\n- `pnpm test` - Run unit tests (vitest)\n\n### Conditionally Required\n\n- `pnpm lint:md` - If editing markdown files\n- `pnpm lint:md:fix` - Auto-fix markdown issues\n- `pnpm run e2e` - For UI/behavior changes (Playwright tests)\n- `pnpm run ladle` - To visually inspect component changes\n\n### Quick Full Validation\n\n- `pnpm ci` - Run complete CI pipeline (setup, build, lint, lint:md, test, e2e)\n- `pnpm format:check` - Check if files are formatted without modifying them\n\n### Fixing Issues\n\nFormat issues are auto-fixed by `pnpm format`. oxlint issues must be fixed manually. Configure your editor to:\n\n- Format on save using oxfmt (140 char width, single quotes, no semicolons)\n- Show oxlint warnings/errors\n\nPre-commit hooks will block commits if lint (which includes type checking) fails.\n\n## Development Workflow\n\n1. Make changes in `packages/react-virtuoso/src/`\n2. Run `pnpm format && pnpm lint && pnpm test`\n3. Check examples with `pnpm run ladle` if UI changes\n4. Run `pnpm e2e` for end-to-end validation if needed\n5. Add changeset with `pnpm changeset-add` for versioned changes\n\n## Git Hooks\n\nThis project uses [lefthook](https://github.com/evilmartians/lefthook) for git hooks.\n\n### Pre-commit Checks\n\nOn every commit, the following checks run automatically on staged files:\n\n- **Code formatting**: Formats `.ts/.tsx/.js/.jsx` files with oxfmt and `.astro` files with prettier; changes are auto-staged via `stage_fixed`\n- **Markdown linting**: Validates .md files with markdownlint\n- **Code linting**: Validates code files with oxlint\n\n### Skipping Hooks\n\nIf you need to skip hooks (e.g., WIP commits):\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: work in progress\"\n# Or use git commit --no-verify (not recommended)\n```\n\n### Hook Management\n\n- Configuration: `lefthook.json`\n- Install hooks: `pnpm exec lefthook install`\n- Uninstall hooks: `pnpm exec lefthook uninstall`\n- Run manually: `pnpm exec lefthook run pre-commit`\n","packages/data-table/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Development Commands\n\n- `pnpm build` - Build the library using TypeScript compiler and Vite\n- `pnpm lint` - Run linting with oxlint (type-aware)\n- `pnpm typecheck` - Run TypeScript type checking without emitting files\n- `pnpm test` - Run tests with Vitest (node and browser projects)\n- `pnpm dev` - Start Ladle development server for component stories\n- `pnpm docs:build` - Generate TypeDoc documentation\n- `pnpm docs:serve` - Serve generated documentation\n- `pnpm format` - Format code with oxfmt\n- `pnpm format:check` - Check formatting without modifying files\n\n## After Making Changes\n\nRun these commands after modifying code:\n\n1. `pnpm format` - Format modified files\n2. `pnpm lint` - Check for lint errors and fix any issues\n3. `pnpm typecheck` - Verify no TypeScript errors\n4. `pnpm test` - Run tests when changes affect component logic, state management, or virtualization behavior\n\nGit hooks (via Lefthook) automatically run formatting and linting on pre-commit, and typecheck + tests on pre-push.\n\n## Architecture Overview\n\nThis is a React virtualized data table component with both column and row virtualization. The architecture is based on a reactive state management system using `@virtuoso.dev/reactive-engine`.\n\n### Core Components\n\n- **VirtuosoDataTable** (`src/VirtuosoDataTable.tsx`) - Main React component that renders the virtualized data table\n- **VirtualizedTableContent** (`src/VirtualizedTableContent.tsx`) - Renders the table content with row and column virtualization\n- **Row** (`src/Row.tsx`) - Individual row renderer\n- **Column** (`src/Column.tsx`) - Column renderer with virtualization\n- **Cell** (`src/Cell.tsx`) - Cell renderer\n- **ColumnHeader** (`src/ColumnHeader.tsx`) - Column header renderer\n- **Data State** (`src/data.ts`) - Holds data cells (`data$`, `context$`, `totalCount$`)\n\n### State Management Architecture\n\nThe component uses a realm-based reactive system with cells:\n\n- **Cells** - Hold state values (e.g., `data$`, `context$`, `totalCount$`)\n- **Realm** - Manages the reactive graph and subscriptions\n\nKey state modules:\n\n- `src/data.ts` - Core data cells\n- `src/dom.ts` - DOM measurements and scroll handling\n- `src/row-state.ts` - Row rendering state management and visible row computation\n- `src/column-state.ts` - Column virtualization state\n- `src/sizes.ts` - Size tree management for virtualization\n- `src/state-flags.ts` - Scroll direction and bottom detection\n\n### Virtualization System\n\n- **Size Tree** (`src/sizing/AATree.ts`) - AA tree implementation for efficient size tracking\n- **Offset Calculations** (`src/sizing/` directory) - Handles item positioning and measurements\n- **Resize Observer** - Tracks item size changes dynamically\n- **Viewport Management** - Renders only visible items with buffer zones\n\n### Scroll Behavior Management\n\n- **Scroll-to-Row** (`src/scroll-to-row.ts`) - Handles programmatic scrolling to specific rows\n- **Smooth Scrolling** (`src/useSmoothScroll.ts`) - Custom smooth scroll implementation\n- **Mobile Safari Fixes** (`src/reverse-scroll-fix.ts`) - Platform-specific scroll behavior corrections\n\n### Testing Infrastructure\n\nTests are organized into two Vitest projects:\n\n- **Node Tests** - Pure logic tests in `tests/node/` and `src/sizing/tests/node/`\n- **Browser Tests** - Component tests using Playwright browser in `tests/browser/` and `src/tests/browser/`\n  - Setup file: `src/tests/browser/setup.ts`\n- **Ladle Stories** - Component stories in `src/_stories/` for development and visual testing\n- **Testing Context** - `VirtuosoDataTableTestingContext` for controlled test environments\n\n### Test Writing Guidelines\n\n**Use exact assertions, not ranges.** Tests must verify precise expected values:\n\n- Use `toBe(9)` not `toBeGreaterThan(0)` or `toBeLessThan(10)`\n- Use `toHaveLength(3)` not `toHaveLength(expect.any(Number))`\n- Calculate expected values from test constants (e.g., `Math.ceil((CONTAINER_HEIGHT - HEADER_HEIGHT) / ROW_HEIGHT)`)\n\n**Every assertion must be derivable from the test setup.** If you set `COLUMN_COUNT = 6` and `CONTAINER_WIDTH = 300` with `COLUMN_WIDTH = 150`, then assert exactly 2 visible columns, not \"less than 6\".\n\n**Avoid false-positive-prone patterns:**\n\n- `toBeGreaterThan(0)` - passes even when value is wrong\n- `toBeLessThan(TOTAL)` - passes even when virtualization is broken\n- `not.toBe(previousValue)` without asserting the new expected value\n\n**Correct pattern:**\n\n```typescript\nconst visibleColumns = Math.floor(CONTAINER_WIDTH / COLUMN_WIDTH)\nexpect(cells.length).toBe(visibleColumns)\n```\n\n### API Surface\n\nThe component is controlled through the reactive engine rather than a component ref API:\n\n- `engineRef` / `engineId` identify the table instance\n- `scrollToRow$` and `scrollIntoView$` handle programmatic scrolling\n- `scrollLocation$` exposes current scroll position information\n- `cancelSmoothScroll$` cancels the current smooth scroll animation\n\n### Key Design Patterns\n\n1. **Reactive State Management** - All state changes flow through the reactive engine reactive system\n2. **Declarative Data with Reactive Control** - Data is passed declaratively via prop; table operations use engine streams\n3. **Performance Optimization** - Only renders visible items with efficient size caching\n4. **Cross-platform Compatibility** - Handles browser-specific scroll behavior differences\n5. **Internal State Ownership** - The table manages its own state internally. External code does not lift state out of the table and pass it back in as props. Instead, the table's internal state is accessible and controllable through an imperative \"remote control\" pattern (e.g., ref methods, command channels). This avoids the lifted-state antipattern where the developer extracts table state, transforms it, and feeds it back, which creates extra render cycles and race conditions.\n\n## Research Guidelines\n\n- **Must not use articles, docs, or source code from** TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid.\n- **Must not reference external libraries** like TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid, or similar - their architecture is irrelevant to this codebase\n- Focus exclusively on understanding and extending the existing patterns in this repository\n","packages/data-table/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2FREADME.md -->","packages/data-table/docs/1.installation/01.shadcn.md":"---\ntitle: Install With shadcn/ui\ndescription: Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.\nsidebar:\n  label: Shadcn Install\n---\n\nThe shadcn registry copies a `DataTable` wrapper into `@/components/ui/data-table`. The wrapper re-exports the headless engine from `@virtuoso.dev/data-table` and ships pre-styled `DataTable`, `DataTableColumn`, `DataTableColumnHeader`, and `DataTableCell`. You edit the wrapper; the headless package stays installed as a dependency.\n\n## Prerequisites\n\n- A React application with TypeScript\n- Tailwind CSS configured\n- shadcn/ui initialized in the project\n- Path aliases resolvable from the root config shadcn reads. If a project uses a solution-style root `tsconfig.json` that only contains `references`, mirror the `@/*` path mapping there or shadcn may create a literal `@/components/...` directory instead of writing into `src/components/...`.\n\n## Add the registry component\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table\n```\n\nThe command writes the wrapper to `@/components/ui/data-table` and imports `@virtuoso.dev/data-table/styles.css` (structural layout defaults). No extra CSS setup is needed.\n\nFeature-specific wrapper components use the same GitHub registry address. For example, add the resize handle when you enable column resizing:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nAdd the sort header button when sortable column headers should dispatch model actions:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe hosted registry URL still works if you prefer the existing virtuoso.dev registry endpoint:\n\n```bash\nnpx shadcn@latest add https://virtuoso.dev/r/data-table.json\n```\n\n## Icon library\n\nThe wrapper uses [`lucide-react`](https://lucide.dev/) for the loading spinner, error icon, drag grip, and sort button icons. The `npx shadcn add` command installs `lucide-react` as a dependency on first run.\n\nshadcn's `iconLibrary` field in `components.json` only rewrites icon imports for the canonical shadcn registry — it does not touch components from third-party registries like ours. If your project uses a different icon set (`@radix-ui/react-icons`, `phosphor-react`, etc.) and you want the table icons to match, open the installed files under `@/components/ui/data-table` and swap the lucide imports for your library's equivalents. The icons appear in:\n\n- `index.tsx` — `Loader2` (spinner), `AlertCircle` (error state)\n- `column-reorder/reorder-grip.tsx` — `GripVertical` (drag handle)\n- `column-sort/sort-header-button.tsx` — `ArrowUpDown`, `ArrowUp`, `ArrowDown` (sort state)\n\n## Basic styled table\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Audio' | 'Office' | 'Peripherals'\n  price: number\n  stock: number\n}\n\nexport const products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319, stock: 9 },\n]\n```\n\n## Next steps\n\nThe wrapper picks up your shadcn theme automatically (border radius, color tokens, typography). Edit `@/components/ui/data-table` directly for custom row hover states, sticky backgrounds, or denser spacing.\n\nThe two required setup steps after installation are [data model](/data-table/data-model/) and\n[columns](/data-table/columns/). For an inventory of what `npx shadcn add` actually wrote into\nyour project and how to edit it, see [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/);\nfor per-instance visual changes, see [Styling](/data-table/customization/styling/).\n\nRemote models, sticky columns, and instance control all import from `@virtuoso.dev/data-table` directly while the wrapper continues to provide the styled `DataTable`.\n","packages/data-table/docs/1.installation/02.headless.md":"---\ntitle: Install The Headless Package\ndescription: Use the headless Virtuoso data table package directly when you need full control over styling and composition.\nsidebar:\n  label: Headless Install\n---\n\nThe headless package gives you `VirtuosoDataTable`, `Column`, `ColumnHeader`, and `Cell` without any preset styling. Pick it when you already have a design system, use non-Tailwind styling, or need fully custom markup around the table.\n\n## Install the package\n\n```bash\npnpm add @virtuoso.dev/data-table\n```\n\n`npm`, `yarn`, and `bun` work too.\n\n## Import structural styles\n\nThe package ships layout defaults (column sizing, box model) in a `@layer virtuoso` block. Import the stylesheet once in your app's CSS or entry point:\n\n```css\n@import '@virtuoso.dev/data-table/styles.css';\n```\n\n```typescript\nimport '@virtuoso.dev/data-table/styles.css'\n```\n\nThe `@layer virtuoso` wrapping puts these rules at the lowest cascade priority, so any plain CSS or Tailwind utility class overrides them.\n\n## Minimal headless example\n\n```tsx live wide file=App.tsx\nimport { Cell, Column, ColumnHeader, VirtuosoDataTable, localModel } from '@virtuoso.dev/data-table'\n\nimport { rows } from './data'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTable\n      model={model}\n      style={{\n        height: 360,\n        border: '1px solid var(--border)',\n        borderRadius: 16,\n        overflow: 'hidden',\n      }}\n    >\n      <Column field=\"name\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Product</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"category\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Category</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"price\">\n        <ColumnHeader>\n          {() => (\n            <div\n              style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}\n            >\n              Price\n            </div>\n          )}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div\n              style={{\n                padding: '0 12px',\n                height: 44,\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'flex-end',\n                fontVariantNumeric: 'tabular-nums',\n              }}\n            >\n              {currency.format(Number(cellValue))}\n            </div>\n          )}\n        </Cell>\n      </Column>\n    </VirtuosoDataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## Next steps\n\nThe headless package exposes the full surface area: local and remote data models, instance control, scroll control, and testing utilities. Continue with [data model](/data-table/data-model/) and [columns](/data-table/columns/).\n","packages/data-table/docs/2.data-model/01.local-data-model.md":"---\ntitle: Local Data Model\ndescription: Use localModel for in-memory rows, derived views, persistence, and grouped data.\nsidebar:\n  label: Local Data Model\n---\n\nWhen the rows are already in the browser — a hardcoded array, React state, a client-side collection mutated by user actions — `localModel()` is the model that wraps them. It holds the array in memory and exposes actions that mutate or derive the rows the table displays.\n\n```typescript\nconst model = localModel({ data: products })\n```\n\nPass the model to the table:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  {/* columns */}\n</DataTable>\n```\n\n## Where to keep the model\n\nThe model holds reactive state — engine subscriptions, the pipeline's stage cache, persisted action payloads. Recreating it discards that state, so the model instance must outlive renders. Two patterns cover almost every case:\n\n**Module scope** — when the dataset is static and the table is a singleton on the page. This is the simplest possible form and matches how most examples in these docs are written.\n\n```tsx\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport function ProductsTable() {\n  return <DataTable model={model}>{/* columns */}</DataTable>\n}\n```\n\nThe model is shared by every render and every mount of `ProductsTable`. Filter and sort state survives navigations away from the page — usually desirable; occasionally not. Initial config can't depend on props because there's no component scope yet.\n\n**`useState` with lazy init** — inside the component, for everything else: multiple table instances on the same page, initial data sourced from props, dynamic mount/unmount.\n\n```tsx\nconst [model] = useState(() => localModel<Product>({ data: initialProducts }))\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Reach for this whenever the model can't live in module scope.\n\nAvoid `useMemo(() => localModel(...), [])` for this purpose. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which would silently reset the model's subscriptions and state. `useState` is the only hook that guarantees retention.\n\n## Pipelines\n\nSource rows rarely match what a table should actually display. A search box trims them down, a header click reorders them, a toolbar groups them by category. A **pipeline** is the ordered chain of stages that derives the rendered rows from the source array — each stage takes the previous stage's output, transforms it, and hands it to the next one.\n\nTwo properties keep the pipeline predictable as actions land:\n\n- The order is **declared once**, so every action lands in a predictable place instead of racing against the others.\n- Each stage's output is **cached**, so an unrelated stage doesn't rerun when only its successor changes.\n\nDeclare the stage order on the model, then attach each handler to the stage it belongs to:\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter', 'sort'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      handler: ({ data, payload }) => data.filter((row) => row.category === payload),\n    },\n    sort: {\n      stage: 'sort',\n      handler: ({ data }) => data.toSorted((a, b) => a.name.localeCompare(b.name)),\n    },\n  },\n})\n```\n\nStage names are open labels — `filter`, `sort`, and `group` are the conventional ones, but you can split work across whatever stages your table needs (`enrich`, `paginate`, `dedupe`). Inside each handler, `data` is whatever the previous stage produced: filter sees the source rows, sort sees the filtered rows, a later group stage would see the sorted ones.\n\nWhen the `filter` payload changes, the filter stage and the later sort stage both rerun. When only `sort` changes, the cached filter result is reused — the difference matters once filtering or grouping touches thousands of rows.\n\n## Source mutators\n\nAn action declared without a `stage` mutates the backing rows. Source mutators invalidate the full pipeline because every derived stage depends on the new source.\n\n```typescript\nconst model = localModel({\n  data: products,\n  actions: {\n    renameProduct: {\n      handler: ({ source, payload }) => source.map((row) => (row.id === payload.id ? { ...row, name: payload.name } : row)),\n    },\n  },\n})\n\nmodel.send({ action: 'renameProduct', payload: { id: 'SKU-001', name: 'Desk Pro' } })\n```\n\n`model.send({ action, payload })` is how buttons, inputs, effects, and table controls dispatch actions. The action name picks the handler; the payload is whatever your handler expects.\n\n## React state updates\n\nWhen rows arrive from React state, a websocket, or a parent workflow, push the next row set into the model instead of recreating it — see [Where to keep the model](#where-to-keep-the-model) for why the instance must stay stable.\n\n```tsx\nconst [model] = React.useState(() => localModel<Product>({ data: initialProducts }))\n\nReact.useEffect(() => {\n  model.setData?.(products)\n}, [model, products])\n```\n\nFor grouped rows, `model.setData(nextRows, nextGroups)` updates rows and markers together.\n\nIf the row schema changes intentionally, remount the table with a new `key` so runtime columns can discover the new first result. See [Runtime Columns](/data-table/columns/runtime-columns/#rediscovering-columns).\n\n### Updating rows in place\n\n`setData` resets the table's size tracking along with the rows — every row is measured again, which briefly unmounts and remounts visible cells. When a refresh only changes row values — a live price tick, a status flip — and the row set is otherwise identical, `model.updateData(nextRows)` skips that reset:\n\n```tsx\nReact.useEffect(() => {\n  model.updateData?.(products)\n}, [model, products])\n```\n\n`updateData` requires the caller to guarantee a strict contract between `nextRows` and the current rows:\n\n- the row count is unchanged\n- each index still holds the same logical row — no insertions, deletions, or reorders\n- `computeRowKey` returns the same key for each row before and after the update\n- group markers are unaffected — `updateData` never takes a `groups` argument, since group placement is index/level metadata a value-only update can't legitimately change\n- row values that do not determine the row key may change freely\n\nCached sizes are retained as estimates for the new values and corrected by `ResizeObserver` for any row that's actually mounted. If the row count doesn't match, the table falls back to a full reset and logs a dev warning.\n\nA pipeline stage that sorts or filters on a field the update just changed can reorder rows at the same indices, which breaks the \"same logical row per index\" contract and misattributes cached heights to the wrong row. `updateData` on a model with a `pipeline` logs a dev warning for this reason — use `setData` there instead.\n\nA custom model (built directly on the model protocol rather than `localModel`) opts into the same behavior by returning `operation: 'update'` on the `DataResult` it emits.\n\n## Grouping\n\nWhen rows naturally cluster — sales by region, products by category, employees by department — group headers belong between the rows they introduce. The library keeps those headers inside the same flat `data` array rather than nesting rows under each header, so the pipeline, sizing, and virtualization treat them as ordinary rows. A separate `groups` array tells the table which entries are headers and how deeply they nest.\n\n### Static groups\n\nWhen the grouping is computed up front, pass pre-grouped rows and markers to `localModel`:\n\n```tsx\nconst rows = [\n  { label: 'Office' },\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  { label: 'Peripherals' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals' },\n]\n\nconst groups = [\n  { index: 0, level: 0 },\n  { index: 3, level: 0 },\n]\n\nconst [model] = useState(() => localModel({ data: rows, groups }))\n```\n\n`index` points at the group row in `data`. `level` is the nesting depth — `0` for top-level headers, `1` for headers nested under them, and so on.\n\n### Multiple levels\n\nHigher `level` values render deeper and stick under their parent when the user scrolls. A two-level `region → country → city` table declares each region marker at `level: 0` and each country marker at `level: 1`:\n\n```typescript\nconst data = [\n  { groupName: 'North America' },\n  { groupName: 'USA' },\n  { name: 'New York', population: 8_336_000 },\n  { name: 'Los Angeles', population: 3_898_000 },\n  { groupName: 'Canada' },\n  { name: 'Toronto', population: 2_794_000 },\n  { groupName: 'Europe' },\n  { groupName: 'UK' },\n  { name: 'London', population: 8_982_000 },\n]\n\nconst groups = [\n  { index: 0, level: 0 }, // North America\n  { index: 1, level: 1 }, // USA\n  { index: 4, level: 1 }, // Canada\n  { index: 6, level: 0 }, // Europe\n  { index: 7, level: 1 }, // UK\n]\n```\n\nThe table treats `level` as a depth hint, not a tree. There's no `parent` field — a `level: 1` header is rendered as a child of whichever `level: 0` header most recently preceded it in `data`, and sticky headers reserve a row per active level on screen. Skipping levels or reordering markers is allowed; the table still renders every row in the order you provided.\n\n### Computing groups in a pipeline\n\nWhen the grouping dimension is interactive — \"group by category\" toggling to \"group by status\" — keep the source rows flat and compute markers inside a `group` pipeline stage. The handler returns `{ data, groups }` instead of a bare array, and the pipeline updates rows and markers atomically:\n\n```tsx\nfunction groupBy<T extends Record<string, unknown>>(rows: T[], field: keyof T | null) {\n  if (!field) return { data: rows, groups: [] as { index: number; level: number }[] }\n\n  const buckets = new Map<string, T[]>()\n  for (const row of rows) {\n    const key = String(row[field])\n    let bucket = buckets.get(key)\n    if (!bucket) buckets.set(key, (bucket = []))\n    bucket.push(row)\n  }\n\n  const data: (T | { label: string })[] = []\n  const groups: { index: number; level: number }[] = []\n  for (const [key, bucketRows] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${String(field)}: ${key} (${bucketRows.length})` })\n    data.push(...bucketRows)\n  }\n  return { data, groups }\n}\n\nconst [model] = useState(() =>\n  localModel({\n    data: products,\n    pipeline: ['filter', 'group'],\n    actions: {\n      filter: {\n        stage: 'filter',\n        handler: ({ data, payload }) => (payload ? (data as Product[]).filter((row) => row.category === payload) : data),\n      },\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupBy(data as Product[], payload as keyof Product | null),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'group', payload: 'category' })\n```\n\nStage order matters here. With `['filter', 'group']`, the filter narrows the source rows and the group stage buckets only what survived — the natural pairing. Putting `group` before `filter` would force the filter handler to special-case header rows, which is rarely worth the complexity.\n\nA handler that returns `{ data, groups }` updates both. Later stages that return a bare array reuse the most recent `groups` from earlier in the pipeline, so a `sort` after `group` doesn't need to know about markers as long as it preserves their indexes (typically by sorting within each bucket).\n\nTo push pre-grouped rows from outside the pipeline entirely — for example after a parent fetch — call `model.setData(nextRows, nextGroups)`.\n\n### Connecting the UI\n\n`<GroupHeaderCell>` renders the entries marked as headers. It receives the original row and the marker's `level`, so styling can branch on nesting depth:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  <GroupHeaderCell>\n    {({ row, level }) => (\n      <div\n        style={{\n          paddingLeft: 12 + level * 16,\n          fontWeight: level === 0 ? 700 : 500,\n          background: level === 0 ? '#e8e8e8' : '#f4f4f4',\n        }}\n      >\n        {(row.data as { groupName: string }).groupName}\n      </div>\n    )}\n  </GroupHeaderCell>\n  {/* columns */}\n</DataTable>\n```\n\n`row.data` is whatever object you pushed at that index in `data` — the shape is yours to define, the cell just renders what you return. Group rows are virtualized like every other row, so keep the rendered markup light.\n\nFor the full render-side reference — remote-paginated grouping, sticky-header behavior, and a runnable end-to-end example — see [Grouped Rows](/data-table/grouped-rows/).\n\n## Action persistence\n\nPersist action payloads — search terms, filter values, sort choice, grouping mode — so user intent survives reloads. Row data is never persisted.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: true,\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPass an object instead of `true` to set a custom storage key or define empty-state handling.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: {\n        key: 'categoryFilter',\n        isEmpty: (value) => !value,\n      },\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPair this with `modelStatePersistenceAdapter()` from [State Persistence](/data-table/state-persistence/) to save model action state alongside column width, order, and visibility.\n\n`isEmpty` skips a payload from saved state when it represents \"no active filter\" — empty strings, `\"all\"` selections, `null` values.\n","packages/data-table/docs/2.data-model/02.remote-data-model.md":"---\ntitle: Remote Data Model\ndescription: Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.\nsidebar:\n  label: Remote Data Model\n---\n\nWhen a table's rows live behind an API and there are too many to load up front — paginated catalogs, search results, infinite feeds — `remoteModel()` is the model that handles the back-and-forth with your backend.\n\nYou give it a `fetch` function that loads a slice of rows. The model calls that function at the right moments (the user scrolled into rows that haven't loaded yet, a search changed, an action fired), feeds the results back into the table, and cancels requests that have been overtaken by newer ones.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n    placeholder: loadingProduct,\n  })\n)\n```\n\n## Where to keep the model\n\nThe model carries live state across renders — requests still in flight, the controllers that can cancel them, persisted action payloads, and the bookkeeping that decides what happens when actions overlap. Recreate the model on every render and that state is thrown away: pending fetches get aborted mid-flight and the next render starts the cycle over with fresh ones. The instance has to outlive renders.\n\nFor most remote tables, **`useState` with lazy initialization** is the right place:\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel<Product, Query>({\n    fetch: fetchProducts,\n    initialParams: { category, search },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n  })\n)\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Use this whenever initial params come from URL state, props, or anything else the surrounding component has access to but a module does not.\n\n**Module scope** works for genuinely static config — a fixed endpoint, no per-route params, one table per page:\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { category: 'all', search: '' },\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nBe aware that every mount of every consumer shares the same instance, and therefore the same fetch lifecycle and persisted action state. That can be a feature (cached results survive navigation) or a footgun (two routes appearing to share filter state).\n\nAvoid `useMemo(() => remoteModel(...), [])`. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which for a remote model means cancelling pending fetches and resetting concurrency state silently. `useState` is the only hook with a retention guarantee.\n\n> The remaining examples on this page write `const model = remoteModel({...})` for brevity, focusing each snippet on the option it's introducing. In real code, wrap the call in `useState(() => ...)` or place it at module scope using the rules above.\n\n## Pagination and infinite scrolling\n\n`remoteModel` ships two built-in patterns for deciding what to fetch as the user scrolls. The right choice depends on how your backend serves data and what kind of scrolling experience the table should deliver.\n\n### Pagination with a known total\n\nReach for **offset mode** when the backend accepts `(offset, limit)` slices and can tell you how many rows exist in total. The table renders placeholder rows for everything that hasn't been fetched, so the scrollbar reflects the full dataset, the user can jump anywhere via the scrollbar or keyboard, and each slice is requested as it enters the viewport.\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nThis is what most search-and-filter UIs want: product catalogs, admin tables, anything backed by a SQL `LIMIT/OFFSET` or an Elasticsearch `from/size` query.\n\n### Infinite scrolling\n\nReach for **append mode** when the backend is cursor-based, the total isn't known up front, or new rows arrive over time — social feeds, message timelines, activity logs.\n\n```typescript\nconst model = remoteModel({\n  mode: 'append',\n  fetch: fetchFeed,\n  onViewportChange: defaultAppendViewportHandler,\n  pageSize: 30,\n})\n```\n\nRows accumulate as the user approaches the end of what's already loaded. The scrollbar only covers the loaded portion because the table can't know how many rows are out there; the loading state shows a footer at the bottom while the next page is fetched.\n\n### Custom fetch windows\n\nWhen neither default matches — the backend uses prefetch hints, variable page sizes, or needs requests coalesced into fewer round-trips — write a custom `onViewportChange`. Given the rendered range and the page size, return one or more slices for the model to fetch:\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: ({ endIndex, pageSize, totalCount }) => {\n    const nextOffset = Math.floor((endIndex + 1) / pageSize) * pageSize\n\n    if (nextOffset < totalCount) {\n      return {\n        fetch: [{ offset: nextOffset, limit: Math.min(pageSize, totalCount - nextOffset) }],\n      }\n    }\n  },\n  pageSize: 50,\n})\n```\n\nOffset-mode handlers return `{ fetch: [{ offset, limit }, ...] }`; append-mode handlers return `{ loadMore: true }`.\n\n## Fetch results\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&search=${params.search}`, {\n    signal,\n  })\n\n  return response.json()\n}\n```\n\nOffset mode expects `{ rows, totalCount }`. Add `groups` when the backend returns a flattened grouped result:\n\n```typescript\nreturn {\n  rows: result.rows,\n  groups: result.groups,\n  totalCount: result.totalCount,\n}\n```\n\nFor backends that define the row schema, return schema metadata next to the rows or keep a separate field list in application state — see [Generating Columns at Runtime](/data-table/columns/runtime-columns/).\n\n`pageSize` controls the slice size. Big enough that ordinary scrolling doesn't request on every small move, small enough that refreshes stay responsive.\n\n`placeholder` is the row shape used while first-page rows are still loading. Include every field your column renderers read.\n\n## Remote actions\n\nRemote actions translate UI intent into the next request's params. URL query parameters are the common case; the same pattern works for cursor filters, request bodies, search objects, or anything else `fetch` reads from `params`.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    actions: {\n      search: {\n        strategy: 'supersede',\n        persistence: true,\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      category: {\n        persistence: { key: 'categoryFilter' },\n        handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'search', payload: 'desk' })\n```\n\nReturning new params triggers a refresh. With `persistence: true`, the payload of each action — the search string, the selected category — is saved alongside the rest of the table's persisted state and replayed through the handler the next time the table loads, so users come back to the same filtered view. Pass `capture` and `restore` instead of `true` when you need a custom serialized shape.\n\nFor stateful controls such as sort, filter, group, or search, prefer the model action state as the source of truth. Seed default active controls with `initialActions`, dispatch changes through `model.send()` or `dispatchModelAction$`, and read `modelActionState$` to paint active buttons. Do not mirror sort/filter payloads in React state just to keep controls highlighted; if a control must be controlled by React, explicitly restore that local state when using `modelStatePersistenceAdapter()`.\n\nFor a complete column-header sort control that sends `{ field, direction }` to a remote model, see [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\n## Concurrency and cancellation\n\n`strategy` controls what happens when an action fires while a fetch is still in flight.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    actions: {\n      search: {\n        strategy: 'supersede',\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      exportRows: {\n        strategy: 'queue',\n        handler: ({ params, payload }) => ({ ...params, exportId: String(payload) }),\n      },\n    },\n  })\n)\n```\n\n- `supersede` — cancels the active request for the same action and uses the latest payload. Right for search boxes and filters.\n- `queue` — runs every request in order. Right for ordered workflows where no request can be dropped.\n- `deduplicate` — collapses identical concurrent requests. Right for retry buttons and repeated load commands.\n\nEvery `fetch` call receives an `AbortSignal`. Pass it to `fetch()` or your client library so superseded refreshes, viewport requests, and disconnects cancel cleanly.\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&q=${params.search}`, {\n    signal,\n  })\n  return response.json()\n}\n```\n\n`onError` fires for failed fetches. Aborted requests publish a loading cancellation event instead.\n\n## Loading state\n\nWhile a fetch is in flight the model emits loading events that the table turns into visible UI — a placeholder for the first load, an overlay for refreshes, and a footer for append-mode pagination. The shadcn wrapper renders sensible defaults for all three slots, so most tables don't need to handle loading state by hand. Reach for the events directly only when something outside the table — a global progress bar, a status badge, a logging hook — needs to react to the same lifecycle.\n\nThree phases drive the visible slots:\n\n- `initial` — first unresolved dataset; rendered as `LoadingPlaceholder`.\n- `refresh` — request params changed while old rows stay visible; rendered as `LoadingOverlay`.\n- `end` — append mode is fetching more rows below the current data; rendered as `LoadingFooter`.\n\nOffset viewport fetches show up as placeholder rows rather than table-level loading and don't update `loadingState$`. Manual `loadRange` calls publish `viewport` events for subscribers, but the loading slots ignore them.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nFor external controls, logging, or diagnostics, subscribe to the model's event channel:\n\n```typescript\nconst unsubscribe = model.subscribe((message) => {\n  if (message.type === 'event' && message.payload?.kind === 'loading') {\n    console.log(message.payload.reason, message.payload.phase)\n  }\n})\n```\n\n`loadingState$` (read via `useVirtuosoLoadingState()` or directly inside the table) is updated from the same `initial`, `refresh`, and `end` events.\n","packages/data-table/docs/2.data-model/03.row-keys.md":"---\ntitle: Row Keys\ndescription: Give the table stable row keys so React reconciles rows correctly when the model reorders them.\nsidebar:\n  label: Row Keys\n---\n\n`computeRowKey` returns the React key for a row — the value React uses to decide whether the row in this render is the same logical row as one from the previous render. Provide a stable key (typically derived from `data.id`), and React reconciles the row in place even when its index changes under a sort, filter, group, prepend, or append.\n\n```tsx\n<DataTable computeRowKey={({ data }) => data.id} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## Why row keys matter\n\nWithout `computeRowKey`, the key falls back to the virtual row position. That works for tables whose rows never move, but as soon as the model reorders — a pipeline `filter` stage runs, a `sort` stage reshuffles the result, a new page is appended, a remote refetch returns rows in a new order — every row whose index changed remounts.\n\nThe symptom is usually a user-visible glitch: a search input loses focus mid-keystroke because the row it lives in moved one position, an expanded detail row collapses because its parent shifted, a chart inside a cell redraws because the cell got a new React identity. The data is still correct; the React identity broke. `computeRowKey` is what stops that from happening.\n\nThis is a React reconciliation rule, not a table-specific concern. It applies to any data table where rows can move, regardless of which other table features you use.\n\n## Grouped data\n\nGrouped data mixes data rows and group rows in the same flat array (see [Grouping](/data-table/data-model/local-data-model/#grouping)). The function receives both shapes, so return a key for each:\n\n```tsx\n<DataTable computeRowKey={({ data, index }) => ('id' in data ? data.id : `group:${data.label}:${index}`)} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nGroup rows rarely carry a stable ID from your data, so a composite key built from a label plus index is fine — the table doesn't try to reconcile group rows across regrouping anyway. The important thing is that data rows keep their identity through reorder; group rows are allowed to remount when the grouping dimension changes.\n\n## Function signature\n\n`computeRowKey` receives an object with three fields:\n\n- `data` — the row at this index (the data row or the group row)\n- `index` — the row's virtual position in the table\n- `context` — the table's [ambient context](/data-table/customization/ambient-context/), useful for scoping keys across tenants, saved views, or other instances that might share row IDs\n\n```tsx\n<DataTable computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`} context={{ tenantId: 'acme' }} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nCross-scope key prefixing matters when one component instance is reused for different tenants or views — without it, React would see the same key for what's actually a different row and skip reconciliation it shouldn't have skipped.\n","packages/data-table/docs/2.data-model/index.md":"---\ntitle: Choosing a Data Model\ndescription: Give the table a local or remote model before adding table features.\nsidebar:\n  label: Choosing a Model\n---\n\nThe model owns the rows; columns own the rendering. Pick the model that matches where rows come from:\n\n- [`localModel()`](/data-table/data-model/local-data-model/) — rows already in the browser, with filter, sort, group, and edit actions deriving the displayed rows.\n- [`remoteModel()`](/data-table/data-model/remote-data-model/) — rows fetched from an API as request params, cursors, or the rendered range change.\n\nBoth models can persist action state (filter values, sort choice, grouping mode) through opt-in adapters. Row data itself is never persisted.\n\n## Basic local model\n\nUse a local model for in-memory rows. Keep the model stable and pass it to `DataTable` with\n`model={model}`.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Where to go next\n\n- [Local Data Model](/data-table/data-model/local-data-model/) — pipelines, source updates, persistence, grouping (single- and multi-level).\n- [Remote Data Model](/data-table/data-model/remote-data-model/) — pagination vs. infinite scrolling, actions, cancellation, grouped rows, loading UI.\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","packages/data-table/docs/3.columns/01.defining-columns.md":"---\ntitle: Defining Columns\ndescription: Declare the fields, order, defaults, and basic behavior of a data-table column.\nsidebar:\n  label: Defining Columns\n---\n\n`DataTableColumn` declares a column. For data-backed columns, pass `field`; it reads `row.data[field]`, uses that value as `cellValue`, and uses the field name as the stable column identity for persistence, reordering, resizing, and visibility.\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>Name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor a row `{ name: 'Standing Desk' }`, the cell renderer receives `'Standing Desk'` as `cellValue`. Omitting `DataTableCell` falls back to the field value; omitting `DataTableColumnHeader` on a field-backed column falls back to the field name. In app UIs, prefer explicit headers so you control capitalization, wording, and accessibility.\n\nDisplay-only columns do not need matching row fields. Give them an explicit `id` instead. The id becomes the stable column identity, and `cellValue` is `undefined` unless you provide an `accessor`.\n\n```tsx\n<DataTableColumn id=\"actions\" sticky=\"right\">\n  <DataTableColumnHeader>Actions</DataTableColumnHeader>\n  <DataTableCell>{({ row }) => <ActionsMenu row={row.data} />}</DataTableCell>\n</DataTableColumn>\n```\n\n`id` is not a visible label. For action, selection, expansion, status-icon, and other display-only columns, always provide a `DataTableColumnHeader`. Use visible text such as `Actions` by default; use `sr-only` text only when the product intentionally wants a headerless icon column.\n\nComputed columns also use an explicit `id`, plus an `accessor` when you want the table to provide a derived `cellValue`.\n\n```tsx\n<DataTableColumn id=\"fullName\" accessor={(row) => `${row.firstName} ${row.lastName}`}>\n  <DataTableColumnHeader>Full name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Minimal columns\n\nAdd an explicit header or cell when you need formatting; leave both off when the field name and stringified value are enough.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 260 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\" />\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Declaration order\n\nColumns render in declaration order. Reordering and persistence change the runtime order; the declaration order stays as the baseline used after a reset.\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"name\" />\n  <DataTableColumn field=\"category\" />\n  <DataTableColumn field=\"price\" />\n</DataTable>\n```\n\n## Visibility and sticky\n\n`visible={false}` hides a column by default. Runtime visibility lives in the [Column Visibility](/data-table/columns/column-visibility/) page.\n\n```tsx\n<DataTableColumn field=\"internalSku\" visible={false} />\n```\n\n`sticky=\"left\"` or `sticky=\"right\"` pins a column for row identity, IDs, or actions that should stay visible during horizontal scrolling — see [Sticky Columns](/data-table/columns/sticky-columns/).\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\" />\n<DataTableColumn id=\"actions\" sticky=\"right\" />\n```\n\n## Generated columns\n\nFor dynamic field lists, map them to `DataTableColumn` declarations. Keep the field order deterministic — the field values become the column identities.\n\n```tsx\nconst fields = ['name', 'category', 'stock']\n\n;<DataTable model={model}>\n  {fields.map((field) => (\n    <DataTableColumn field={field} key={field}>\n      <DataTableColumnHeader>{field}</DataTableColumnHeader>\n      <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n    </DataTableColumn>\n  ))}\n</DataTable>\n```\n\nWhen the field list itself is discovered from the first data result, see [Runtime Columns](/data-table/columns/runtime-columns/).\n","packages/data-table/docs/3.columns/02.cell-and-header-renderers.md":"---\ntitle: Formatting Cells and Headers\ndescription: Use render params to format cells and headers from column and row state.\nsidebar:\n  label: Formatting Cells\n---\n\nMost columns need formatting that goes beyond stringifying a field — currency values, status badges, stock-level colors, multi-line product names. `DataTableCell` and `DataTableColumnHeader` are where that formatting lives. Each one accepts a render function that the table calls when it paints a body cell or header; only cells inside (or close to) the viewport actually run, so the JSX stays a static, declarative description of the structure while the work scales to large tables.\n\n## Style the correct header surface\n\n`DataTableCell` accepts a `className` prop alongside its render function. The class lands on the table's own cell wrapper, so use it for alignment, font weight, text color, `tabular-nums`, and sticky-specific colors instead of wrapping the render output in another `<div>`.\n\n```tsx\n<DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n```\n\nHeaders have two surfaces because a grow column has both a base width and a rendered width. The table observes the intrinsic header to determine the base width, then assigns spare space to growing columns. If the observed element also stretches across that spare space, its rendered width becomes the next base measurement and the column cannot shrink when the table narrows. Keeping measurement and presentation on separate elements prevents that feedback loop.\n\n`className` styles the intrinsic header content that the table measures. Use it for width, minimum width, padding, typography, and alignment within the base width. `containerProps` styles the outer container after the table assigns the rendered column width. Use it for paint and interaction that must cover the full column, such as a background, hover state, outline, box shadow, cursor, ARIA attribute, or event handler.\n\n```tsx\n<DataTableColumnHeader\n  className=\"min-w-64\"\n  containerProps={{\n    className: 'cursor-pointer hover:bg-muted/50',\n  }}\n>\n  Product\n</DataTableColumnHeader>\n```\n\nThe outer container's geometry belongs to the table. Do not put width, flex, margin, padding, border, display, or `justify-*` utilities on `containerProps`; these can clip the measured content or move edge-aligned header slots. Keep those layout styles on `className`, use `box-shadow` or `outline` for full-width separators, and use `HeaderEnd` for a control that must stay at the rendered column edge.\n\nEvents attached through `containerProps` receive bubbled events from header slots and nested controls. When a header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nKeep width utilities such as `w-*` and `min-w-*` on `DataTableColumnHeader.className`. A `w-full` or flex-growth class on this intrinsic surface resolves only within the measurement boundary; it does not cover the rendered track. Use `containerProps` for that visual result. Do not put width utilities on `DataTableCell`. Use `DataTableColumn grow={...}` when a column should absorb leftover horizontal space. A cell width can make the body content fight the measured grid track and overlap neighboring columns. For cell content that needs truncation or nested layout, keep the cell unconstrained and put `min-w-0` on an inner wrapper:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-64\">Name</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ row }) => <div className=\"min-w-0 truncate\">{row.data.name}</div>}</DataTableCell>\n</DataTableColumn>\n```\n\nEach visible cell is its own element; the table also column- and row-virtualizes, so an extra wrapper inside the render function multiplies by every rendered cell on every scroll. Save nested elements for styling that's only part of the cell content — a status badge, an inline icon, a secondary line of metadata.\n\nThe shadcn wrapper accepts plain text for static headers:\n\n```tsx\n<DataTableColumnHeader>Product</DataTableColumnHeader>\n```\n\nAnd a render function for headers that depend on table state:\n\n```tsx\n<DataTableColumnHeader>{({ columnState }) => (columnState.sticky ? 'Pinned product' : 'Product')}</DataTableColumnHeader>\n```\n\n## Cell values and rows\n\n`cellValue` is the selected field value or the result of the column `accessor`; `row.data` is the whole row. Reach for `row.data` whenever the cell depends on more than one field or renders UI that is not data-backed.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : `${row.data.stock} in stock`}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Render params\n\nThe render function receives a single object whose fields surface what you'd otherwise have to look up by hand. `cellValue` is the shortcut for `row.data[column.field]` on field columns, the result of `accessor(row.data)` on computed columns, and `undefined` on id-only display columns. `row.data` is there for cells that depend on more than one field. `column` and `columnKey` identify which column this invocation is rendering — useful when one extracted renderer drives multiple columns. `column.id` is the stable public identity when you provided one; `column.field` exists only for columns that read a row-data field. `columnState` lets the renderer branch on runtime column state (currently just `sticky`, so headers and cells can style differently when the column is pinned). `overlaidByScrollbar` flips to `true` when a native OS scrollbar overlaps the cell's edge — that's the table telling you trailing UI in this cell is unreachable, so move buttons or actions inboard while the flag is set. Header params have the same shape minus `cellValue` and `row` — a header has no row to render against.\n\n```typescript\ntype CellRenderParams = {\n  cellValue: unknown // row.data[column.field], accessor(row.data), or undefined\n  row: RowItem // the virtualized row\n  column: ColumnDefinition\n  columnKey: string // internal key for this column instance\n  columnState: ColumnState // currently: { sticky }\n  overlaidByScrollbar: boolean // true when a native scrollbar overlaps the cell edge\n}\n\ntype ColumnHeaderRenderParams = Omit<CellRenderParams, 'cellValue' | 'row'>\n```\n\n```tsx\n<DataTableColumnHeader>\n  {({ column, columnState }) => <span className={columnState.sticky ? 'font-semibold' : undefined}>{column.field}</span>}\n</DataTableColumnHeader>\n```\n\n## Extracting renderers\n\nInline render functions become unwieldy past a couple of lines, and they limit what you can do with the renderer itself. Pulling them out into named functions — or full components — buys three things at once: they appear by name in React DevTools (instead of as anonymous arrow functions), one renderer can drive several columns that share the same formatting, and you can wrap the renderer in `React.memo` so it skips re-renders when the row data hasn't changed — useful for expensive cells like rich text or chart sparklines.\n\nThe render-param types are exported and double as the props type for the component:\n\n```tsx\nimport type { CellRenderParams, ColumnHeaderRenderParams } from '@virtuoso.dev/data-table'\n\nfunction StockCell({ row }: CellRenderParams) {\n  return <span>{row.data.stock < 20 ? 'Low stock' : 'Healthy'}</span>\n}\n\nfunction FieldHeader({ column }: ColumnHeaderRenderParams) {\n  return <span>{column.field ?? column.id}</span>\n}\n```\n\nExtracted renderers are ordinary React components. They can call hooks, subscribe to React context, and compose with `React.memo` like any other component. One thing they _don't_ receive is the table's `context` prop — that channel exists for table infrastructure (empty-state, sticky wrappers, scroll element) and doesn't reach cell or header renderers. Close over screen-local state directly, or read it through React context if multiple columns need it.\n\nPass renderers as children of the cell or header:\n\n```tsx\n<DataTableColumn field=\"stock\">\n  <DataTableColumnHeader>{FieldHeader}</DataTableColumnHeader>\n  <DataTableCell>{StockCell}</DataTableCell>\n</DataTableColumn>\n```\n\n## Header slots\n\nA header can host additional UI alongside the label — sort buttons, filter menus, resize handles, drop indicators for reordering. Inline UI (buttons, menus) can go in the header's render function. Two positions don't fit there: a resize handle has to sit on the column boundary independent of label alignment, and a drop indicator has to span the entire header during a reorder drag. The table provides four named slots for these cases:\n\n- `HeaderStart` — before the label, in the header's flex layout. Drag grips, leading icons.\n- `HeaderEnd` — after the label, in the same flex layout. Sort buttons, menu triggers.\n- `HeaderEdge` — pinned to the column boundary, outside label layout. Resize handles.\n- `HeaderOverlay` — spans the whole header. Drop indicators, focus rings.\n\nDeclare slots inside the header; the table positions each one according to its slot type. Slot children receive the same render params as the header, so a sort button can read `column.field` to know which column it's acting on.\n\n```tsx\n<DataTableColumnHeader>\n  {() => 'Price'}\n  <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n</DataTableColumnHeader>\n```\n\nWhen a header has any slots, the label must be a render function — plain string children are only valid for headers without slots. That's why the example above writes `{() => 'Price'}` instead of just `Price`.\n\nThe shadcn wrapper's resize and reorder UI is built on these slots. See [Header Slots](/data-table/customization/header-slots/) for the placement diagram, the `component` prop alternative to render-function children, and complete recipes for sort, filter, and menu slots.\n","packages/data-table/docs/3.columns/03.column-groups.md":"---\ntitle: Column Groups\ndescription: Organize related columns under shared headers.\nsidebar:\n  label: Column Groups\n---\n\n`ColumnGroup` wraps adjacent columns and renders one shared header above them — \"Inventory\" sitting above stock and status, \"Fulfillment\" above region and category. In a wide table, the grouped header gives the reader a second-tier band to scan instead of counting columns from the left edge to locate a field. Use it when neighbouring columns belong together as a named bundle (inventory metrics, fulfillment details, customer attributes) and the table is wide enough that the bundle isn't obvious from column order alone.\n\n```tsx live wide\nimport { localModel } from '@virtuoso.dev/data-table'\nimport {\n  ColumnGroup,\n  ColumnGroupHeader,\n  DataTable,\n  DataTableCell,\n  DataTableColumn,\n  DataTableColumnHeader,\n} from '@/components/ui/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', category: 'Office', stock: 14, status: 'Ready', region: 'US' },\n  { name: 'USB-C Dock', category: 'Device', stock: 42, status: 'Ready', region: 'EU' },\n  { name: 'Keyboard', category: 'Device', stock: 8, status: 'Low', region: 'APAC' },\n]\n\nconst model = localModel({ data: rows })\nconst groupHeaderClassName = 'flex h-8 items-center justify-center bg-muted/40 px-2 text-xs font-medium text-muted-foreground'\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 300 }}>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Catalog'}</ColumnGroupHeader>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Inventory'}</ColumnGroupHeader>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Fulfillment'}</ColumnGroupHeader>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n    </DataTable>\n  )\n}\n```\n\nGrouping changes only the header. Each column inside a group keeps its own field, cell renderer, width, visibility, and saved state — the body grid never notices the grouping exists. That separation is what makes the next sections work: every per-column feature still applies, with a few group-level twists worth knowing.\n\n## Sticky groups\n\nPin a whole group with `<ColumnGroup sticky=\"left\">` (or `\"right\"`). Every column inside follows the group to the edge during horizontal scroll — it's a shorthand for declaring `sticky=\"left\"` on each child, and the group header stays anchored above its columns.\n\n```tsx\n<ColumnGroup sticky=\"left\">\n  <ColumnGroupHeader>{() => 'Identity'}</ColumnGroupHeader>\n  <DataTableColumn field=\"id\">…</DataTableColumn>\n  <DataTableColumn field=\"name\">…</DataTableColumn>\n</ColumnGroup>\n```\n\nOnly top-level groups can be sticky — `sticky` on a nested `<ColumnGroup>` is ignored. Children inherit sticky-ness from the nearest sticky ancestor, so nested groups don't need their own opt-in.\n\n## Resizing inside a group\n\nResize handles work unchanged on columns inside groups. Each column owns its width override; the group header automatically re-sums from its children on every resize, so dragging \"Stock\" wider stretches the \"Inventory\" band to match. You never set a width on the group itself — there's no group-level resize API, because distributing pixels across child columns is rarely what users mean when they grab a header.\n\n## Reordering with groups\n\nTwo reorder gestures coexist:\n\n- **Move a whole group.** `DraggableGroupHeader` (from the shadcn `column-reorder` registry) makes the group header itself draggable. The user drags \"Inventory\" between \"Catalog\" and \"Fulfillment\" and all of Inventory's columns travel together, keeping their internal order.\n- **Move one column.** `ReorderGrip` on individual column headers keeps working — useful for nudging a column within its group.\n\nBoth gestures respect sticky regions: a left-sticky source can only land among other left-sticky targets, and so on. That guard is built into the registry components, so you can drop them in without filtering by hand.\n\nThere's a footgun in mixing the two: **a single-column drag that crosses a group boundary breaks the visual layout.** The header tree pairs each column with its declared `<ColumnGroup>` regardless of where it now sits in column order, so a column dragged out of group A and dropped between group B's columns produces a body row in the new order but a header band that's no longer contiguous. The whole-group drag never has this problem — it moves every key in one transaction.\n\nTwo practical responses:\n\n- Mount `ReorderGrip` only on ungrouped columns (or only inside groups, never both), so users can't initiate a cross-group single-column drag.\n- Or keep both gestures and accept that cross-group moves split the band. Works for tables where the grouping is informational rather than structural.\n\n`reorderColumnGroup$` is the underlying engine action if you wire your own group-drag UI; `DraggableGroupHeader` publishes to it. See [Column Reordering](/data-table/columns/column-reordering/) for the full reorder surface.\n\n## Nesting\n\n`<ColumnGroup>` can wrap other `<ColumnGroup>` declarations for two-tier categorization — \"Customer\" above \"Personal\" and \"Account\", each above their own columns:\n\n```tsx\n<ColumnGroup>\n  <ColumnGroupHeader>{() => 'Customer'}</ColumnGroupHeader>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Personal'}</ColumnGroupHeader>\n    <DataTableColumn field=\"name\">…</DataTableColumn>\n    <DataTableColumn field=\"email\">…</DataTableColumn>\n  </ColumnGroup>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Account'}</ColumnGroupHeader>\n    <DataTableColumn field=\"plan\">…</DataTableColumn>\n  </ColumnGroup>\n</ColumnGroup>\n```\n\nEach nesting level stacks one extra header row. Two levels usually covers what's worth grouping; deeper hierarchies steal vertical space the data needs.\n\n## Layout notes\n\n- Each nesting level adds a full header row. Keep group labels short and the row height modest — `text-xs` with small padding is the usual register.\n- Group headers render full-width over their span. The label sits inside that bar, so very long labels either wrap or get clipped depending on your CSS.\n","packages/data-table/docs/3.columns/04.sticky-columns.md":"---\ntitle: Sticky Columns\ndescription: Pin high-value columns to the left or right side of a wide table.\nsidebar:\n  label: Sticky Columns\n---\n\nPass `sticky=\"left\"` or `sticky=\"right\"` to a `DataTableColumn` and it stays in place while the rest of the row scrolls under it horizontally. Use this to keep row-identifying columns (typically a product name) visible on the left, and status or action columns visible on the right, when the table is wide enough to scroll sideways.\n\nTwo layout constraints have to hold for the effect to work:\n\n- Pinned columns need an explicit minimum width on the header (`min-w-[…]` on `DataTableColumnHeader`). Without one, the column can collapse to its content width and overlap the scrollable cells sliding past it. See [Column Resizing](/data-table/columns/column-resizing/#where-column-widths-live) for why width belongs on the header, not the cell.\n- Every pinned column subtracts from the scrollable area. Pin one or two columns per edge — pinning more shrinks the scroll region until the feature stops earning its keep.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  owner: ['Avery', 'Morgan', 'Riley'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n  supplier: ['Northwind', 'Contoso', 'Fabrikam'][index % 3]!,\n  warehouse: ['Denver', 'Rotterdam', 'Singapore'][index % 3]!,\n  price: 49 + (index % 8) * 24,\n  stock: (index * 7) % 64,\n  leadTime: `${2 + (index % 5)} days`,\n  margin: `${18 + (index % 6) * 3}%`,\n  updated: `2026-05-${String(1 + (index % 17)).padStart(2, '0')}`,\n}))\n\nconst model = localModel({ data: rows })\nconst scrollColumns = [\n  ['category', 'Category'],\n  ['owner', 'Owner'],\n  ['region', 'Region'],\n  ['supplier', 'Supplier'],\n  ['warehouse', 'Warehouse'],\n  ['price', 'Price'],\n  ['stock', 'Stock'],\n  ['leadTime', 'Lead time'],\n  ['margin', 'Margin'],\n  ['updated', 'Updated'],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\" sticky=\"left\">\n        <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      {scrollColumns.map(([field, label]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader className=\"min-w-[140px]\">{label}</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n      <DataTableColumn field=\"status\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe relevant lines, isolated:\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\">\n  <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"status\" sticky=\"right\">\n  <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Changing pinning at runtime\n\n`sticky` on `DataTableColumn` sets the initial pinning. When the user needs to pin or unpin a column from a toolbar — say, a \"Pin to left\" menu item — publish to the table's `setColumnSticky$` channel instead of re-rendering with a new prop. The [Sticky Columns example](/data-table/examples/sticky-columns/) wires up that external toggle end-to-end; [Controlling the Table](/data-table/controlling-the-table/) covers the engine API the channel belongs to.\n","packages/data-table/docs/3.columns/05.column-visibility.md":"---\ntitle: Column Visibility\ndescription: Hide and restore columns with declarative defaults, reactive controls, and persistence.\nsidebar:\n  label: Column Visibility\n---\n\nColumn visibility has two flavours: a declarative default that hides a column at mount, and a runtime channel that flips visibility from a \"Columns\" menu, a saved view, or a similar toggle. The two compose — declare hidden defaults for columns like internal IDs, audit timestamps, and other secondary metadata, then expose a runtime control for users who need to opt back in.\n\n```tsx live wide\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { columns$, localModel, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n]\n\nfunction labelFor(field: string) {\n  return field.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"flex flex-wrap items-center gap-3 rounded-lg border bg-muted/30 px-3 py-2 text-sm\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n\n          return (\n            <label key={key} className=\"flex items-center gap-2\">\n              <input checked={visible} onChange={() => setColumnVisibility({ key, visible: !visible })} type=\"checkbox\" />\n              {labelFor(column.field)}\n            </label>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} size=\"sm\" variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 280 }}>\n        <DataTableColumn field=\"product\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Hiding at declaration time\n\nPass `visible={false}` to a `DataTableColumn` and it stays out of the rendered grid:\n\n```tsx\n<DataTableColumn field=\"internalId\" visible={false}>\n  <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nThe column is still _registered_ — that's what makes it selectable in a picker. It just doesn't take a slot in the header, body, or scroll measurement. There's no layout cost beyond keeping its JSX in the tree, so this is also the right shape for \"hidden by default, sometimes useful\" columns that don't need a runtime toggle.\n\n## Toggling at runtime\n\nPublish to `setColumnVisibility$` (from `@virtuoso.dev/data-table/column-visibility`) with a runtime column key and the boolean you want the column to end up at:\n\n```tsx\nsetColumnVisibility({ key, visible: !isVisible })\n```\n\nRead the current state from `columnVisibilityState$` — a `Map<key, boolean>` with one entry per registered column, declarative defaults already folded in. Consumers don't have to fall back to `columns$` for \"what's the default again?\"; the resolved value is right there.\n\n`resetColumnVisibility$` (a `Trigger`, no payload) clears every override at once — typically wired to a \"Reset to defaults\" button. And there's a quieter convenience worth knowing: when the user toggles a column back to its declarative default, the override is dropped instead of stored as a redundant entry. \"Hide then show\" returns the table to a clean state, and the saved-state side of the feature stays minimal as a result.\n\n## Where the controls live\n\nThe demo above sits beside the table and connects through `engineRef`, using `useRemoteCellValue` and `useRemotePublisher`. That's the common shape — a toolbar, a preset menu, a settings panel — anywhere the controls aren't a child of `<DataTable>`.\n\nFor controls mounted _inside_ the table tree — a header slot, a footer, a context menu rendered from a cell — the simpler `useCellValue` and `usePublisher` hooks from `@virtuoso.dev/reactive-engine-react` work, picking up the same engine through React context:\n\n```tsx\nimport { columns$ } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\nimport { useCellValue, usePublisher } from '@virtuoso.dev/reactive-engine-react'\n\nfunction ColumnVisibilityPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div>\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key)\n        return (\n          <button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} type=\"button\">\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n```\n\n[Controlling the Table](/data-table/controlling-the-table/) covers the in-tree-vs-sibling distinction in depth.\n\n## How visibility composes with other column features\n\n- **Groups.** Hiding a column shrinks its `ColumnGroup` header band to whatever children remain visible. Hide every column in a group and the group header disappears with them — there's no empty band.\n- **Sticky.** A hidden sticky column simply vanishes; the columns next to it stay where they are rather than sliding into the pinned region.\n- **Reorder.** Visibility and order are independent — hiding a column doesn't change its position in `columns$`, and showing it again restores it to the slot it held before.\n\n## Saving the user's choice\n\n`columnVisibilityPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to save visibility across reloads. The saved shape is keyed by stable field name and contains only the _differences from declarative defaults_, so:\n\n- A user who hasn't touched the picker saves nothing.\n- A column that started visible and stays visible never appears in saved state.\n- Hiding a default-visible column writes one entry; un-hiding it drops the entry back out.\n\nThat makes the saved bundle forward-compatible: adding a new column to the JSX with a sensible declarative default doesn't surprise existing users with a stale override on next load. The catch sits on the other side — renaming a `field` invalidates the saved visibility for that field, since the saved state can no longer find the column it was about. The [field-name persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\nSee the [Column Visibility example](/data-table/examples/column-visibility/) for a runnable sibling-toolbar setup.\n","packages/data-table/docs/3.columns/06.column-resizing.md":"---\ntitle: Column Resizing\ndescription: Let users change column widths with the opt-in resize model and shadcn resize handle.\nsidebar:\n  label: Column Resizing\n---\n\nA column's default width comes from its content and the table's layout — usually fine, sometimes not. Long product names need more room than the default; an ID column can shrink. Column resizing gives users a drag handle on the header edge to set the width themselves, and the table remembers their choice once persistence is wired up.\n\n## Adding the drag handle\n\nMount `ResizeHandle` from the shadcn `data-table-resize-handle` registry item inside a `HeaderEdge` slot. The handle owns the pointer interaction, the live preview while dragging, and the publish of the final width back to the table — the column declaration only has to expose the slot.\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-resize`.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 60 }, (_, index) => ({\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe slot alone is what enables resizing on a column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderEdge component={ResizeHandle} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nSkip `HeaderEdge` for columns the user shouldn't be allowed to resize — a fixed-width row-selector column, for example.\n\n## Where column widths live\n\nThe table owns every column's width through its header. It measures each `DataTableColumnHeader`, distributes any leftover viewport space across the visible columns, and uses the resulting size for every body cell in that column. The drag handle, the programmatic `resizeColumn$` call, and the persisted overrides feed into that same pipeline as user-selected rendered widths. While a resize override is active, sibling columns keep their current rendered widths so dragging a boundary feels direct and can create horizontal overflow.\n\nTo set a column's _base_ width, style the header with width or minimum-width classes. To decide which columns receive leftover horizontal space, use `DataTableColumn grow={number}`. Do not use Tailwind flex growth classes as the table sizing API:\n\n```tsx\n<DataTableColumn field=\"id\">\n  <DataTableColumnHeader className=\"w-20\">ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-48\">Product</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor the full sizing model, including fixed metadata columns, text-heavy grow columns, horizontal overflow, and resize override behavior, see [Column Layout](/data-table/columns/column-layout/).\n\n:::caution[Don't put widths on the cell]\nBody cells render inside a grid track sized to the column's measured width — they're already constrained from the outside. Width CSS on `DataTableCell` (`w-*`, `min-w-*`, `max-w-*`, `flex-*`, `basis-*`) doesn't influence the column and can fight the grid into clipped or overflowing content. When a column needs to be wider, widen its header. Reserve cell CSS for things inside the track: padding, alignment, typography, truncation.\n:::\n\nThis includes width pressure added for \"realistic\" content. If a prompt name, badge stack, action menu, or timestamp needs room, express that as a header width:\n\n```tsx\n<DataTableColumn field=\"updated_at\">\n  <DataTableColumnHeader className=\"min-w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror that width on the body cell. For multi-line or nested content, place layout helpers inside the cell renderer:\n\n```tsx\n<DataTableCell>\n  {({ row }) => (\n    <div className=\"min-w-0\">\n      <p className=\"truncate\">{row.data.name}</p>\n      <p className=\"truncate text-muted-foreground\">{row.data.slug}</p>\n    </div>\n  )}\n</DataTableCell>\n```\n\nAs a final review step, search for width utilities on cell declarations:\n\n```bash\nrg 'DataTableCell.*className=.*(w-|min-w|max-w|basis-|grow|shrink|flex-(none|auto|initial|1|\\[))'\n```\n\n## Resizing without the drag handle\n\nSome resizes don't originate from the column edge. A \"fit to content\" toolbar button, a \"Compact / Comfortable / Spacious\" preset, a keyboard shortcut — each needs to set the width without anyone dragging. Publish to `resizeColumn$` directly:\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n\nfunction fitNameColumn() {\n  const nameKey = [...(columns ?? new Map())].find(([, column]) => column.field === 'name')?.[0]\n  if (nameKey) {\n    resizeColumn({ key: nameKey, width: 280 })\n  }\n}\n```\n\nThe payload's `key` is the column's internal identifier, not its `field`. The lookup against `columns$` translates a stable, user-facing field name into the key the stream expects.\n\n## Persisting widths across reloads\n\nThe handle records the user's _intent_ — \"the user wants the name column 280px wide.\" Intent is what gets saved. Automatic `grow` output is not persisted; clearing the override lets the column fall back to its measured base width and the current grow distribution.\n\nMount `columnWidthPersistenceAdapter()` on `DataTableStatePersistence` to persist overrides. The adapter saves them keyed by `field`, so the saved entry survives reordering, partial column lists, and dynamically generated columns. [State Persistence](/data-table/state-persistence/) covers the full wiring, including the field-name contract that ties saved state to column declarations.\n\n## The full resize surface\n\nThree publishers cover the runtime mutations:\n\n- `resizeColumn$` — set a width override for one column.\n- `clearColumnWidthOverride$` — drop one override; the column falls back to its default.\n- `resetColumnWidthOverrides$` — drop every override at once, e.g. a \"Reset widths\" menu item.\n\nOne adapter wires those into persistence:\n\n- `columnWidthPersistenceAdapter()` — saves the override map; see [State Persistence](/data-table/state-persistence/).\n\nOne read-only cell exposes the realized layout, for layout-aware UI that needs the actual rendered widths:\n\n- `columnWidths$` — realized widths after distribution. Read for layout introspection; do **not** persist these values.\n\nThe [Column Resizing example](/data-table/examples/column-resizing/) puts the handle, preset menus, and persistence together in one table.\n","packages/data-table/docs/3.columns/07.column-reordering.md":"---\ntitle: Column Reordering\ndescription: Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.\nsidebar:\n  label: Column Reordering\n---\n\nColumn reordering lets the user move columns into a different position than the JSX declares. The order in JSX becomes the initial order; the user changes it through a drag UI mounted in the header, a toolbar action, or programmatic engine calls.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 320 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n## Wiring the handles\n\nTwo pieces mount inside each column header: a draggable grip the user can grab, and a drop zone that paints the indicator line when another column is dragged over. They live in two different header slots so they don't fight each other for space — the grip sits in `HeaderStart` next to the title, and the drop indicator overlays the whole header through `HeaderOverlay` without disturbing layout.\n\n```tsx\nimport { HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\n;<DataTableColumn field=\"status\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Status'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n`ReorderGrip` and `ReorderDropZone` are shadcn registry components — drop them into a column header and reordering works. They're not the whole story, though: under them is an engine action any other surface can publish to.\n\n## Reordering from a toolbar or menu\n\nDrag isn't the only way to move columns. A \"Move to first\" menu item or an arrow-key shortcut can reach for `reorderColumns$` from outside the header. The shape is the same as what the drag UI publishes: source, target, and a `before` / `after` position.\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { reorderColumns$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst reorderColumns = useRemotePublisher(reorderColumns$, engineRef)\n\nconst entries = [...(columns ?? new Map())]\nconst statusKey = entries.find(([, column]) => column.field === 'status')?.[0]\nconst nameKey = entries.find(([, column]) => column.field === 'name')?.[0]\n\nif (statusKey && nameKey) {\n  reorderColumns({ sourceKey: statusKey, targetKey: nameKey, position: 'before' })\n}\n```\n\nThe two-step lookup is genuine API friction worth knowing: `reorderColumns$` operates on runtime _column keys_ (the identity the engine uses internally), not the `field` names you write in JSX. Most callers know the field — \"move 'status' before 'name'\" — and translate by walking `columns$` once. Column keys are stable for a given declaration, so for repeated programmatic moves it's worth building the field → key map once at mount and stashing it in a ref.\n\n## Resetting to declaration order\n\nFor a \"Reset to default order\" button, publish `resetColumnOrderToDeclaration$`. The table already tracks the current component declaration order internally, so the caller doesn't need to snapshot runtime column keys at mount.\n\n```tsx\nimport { useEngineRef, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnOrderToDeclaration$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst resetColumnOrder = useRemotePublisher(resetColumnOrderToDeclaration$, engineRef)\n\n;<button onClick={() => resetColumnOrder()} type=\"button\">\n  Reset order\n</button>\n```\n\nIf `columnOrderPersistenceAdapter()` is mounted, the reset is treated like any other order change and the declaration-order state is saved under the table's persistence key.\n\n## Sticky and group constraints\n\nTwo rules narrow what the drag UI accepts, and they apply equally to engine-level reorders:\n\n- **Sticky stays with sticky.** A drag from a `sticky=\"left\"` column only accepts drops on other left-sticky targets; right-sticky behaves the same. Non-sticky columns can't be pulled into a pinned region by the drag UI, and the engine action treats sticky-ness as a column property rather than a positional one — moving a non-sticky column next to a pinned one doesn't make it pinned.\n- **Group drag is its own gesture.** When columns live inside a `ColumnGroup`, the group header itself can be made draggable with `DraggableGroupHeader`, moving every member as one transaction. The single-column gesture stays available, but dragging an individual column across group boundaries breaks the visual band — see [Column Groups → Reordering with groups](/data-table/columns/column-groups/#reordering-with-groups) for the full picture and the practical mitigations.\n\n## Saving the user's order\n\n`columnOrderPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to keep the user's order across reloads. The saved shape is a list of `field` names — stable, declarative identifiers — rather than the runtime keys the engine uses internally. As a result:\n\n- Adding a new column to the JSX places it at its declared position on next load. Saved state doesn't list it, so the new column doesn't get a stale slot somewhere arbitrary.\n- Removing a column drops it from saved state without disturbing the others.\n- Renaming a `field` invalidates that entry's saved position — the saved state can't find the column it was about. [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\n## Building your own drag UI\n\nThe shadcn grip and drop zone are a reference implementation. If your design system uses a different drag library, you want long-press for touch, or the drop indicator needs custom positioning, the underlying coordination primitives are public:\n\n- `columnDragState$` — a reactive cell holding \"what's being dragged, where's the drop indicator.\" Grip writes, drop zone reads.\n- `beginColumnDrag$` / `endColumnDrag$` — lifecycle channels published from the grip's `dragstart` / `dragend`.\n- `setColumnDropTarget$` — what the drop zone publishes from `dragover`.\n\nThe source for `ReorderGrip` and `ReorderDropZone` in the shadcn registry is short and self-contained; copy it as a starting point and swap out the markup that doesn't fit.\n\nSee the [Column Reordering example](/data-table/examples/column-reorder/) for a runnable setup with persistence wired in.\n","packages/data-table/docs/3.columns/08.runtime-columns.md":"---\ntitle: Generating Columns at Runtime\ndescription: Discover and declare table columns when the field list is only known after data arrives.\nsidebar:\n  label: Runtime Columns\n---\n\nSome tables cannot name every column at build time. An admin viewer may render tenant-defined fields, a debug tool may inspect arbitrary JSON, or a report may receive its visible fields from the first data result.\n\n`DynamicColumns` is for that case. It waits for the first non-empty data result, calls your render function with that data, and lets you return normal `DataTableColumn` declarations. The columns stay fixed for the lifetime of that table instance — if the dataset shape changes later, remount the table with a new `key`.\n\n## What the render function receives\n\nThe render function is called with `{ data, model }`:\n\n- `data` is the first non-empty array the table captured. It does not change when the model later fetches more rows, filters, or fills in remote placeholders — you scan it once and produce columns from what's there.\n- `model` is the same handle you passed to `DataTable`, available for the rare case where a generated header or cell needs to publish a model action.\n\nField scanning lives in your app code so the rules — which fields to include, what counts as a sample value, how to handle group rows — stay obvious next to the table. A general-purpose starting point looks like this:\n\n```ts\ntype RuntimeField = {\n  field: string\n  sampleValue: unknown\n}\n\nfunction fieldsFromRows<Row>(rows: readonly Row[], includeField: (field: string) => boolean = () => true): RuntimeField[] {\n  const fields: RuntimeField[] = []\n  const fieldByName = new Map<string, RuntimeField>()\n  const hasSample = new Set<string>()\n\n  for (const row of rows) {\n    if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n      continue\n    }\n\n    for (const [field, value] of Object.entries(row)) {\n      if (!includeField(field)) {\n        continue\n      }\n\n      let descriptor = fieldByName.get(field)\n      if (!descriptor) {\n        descriptor = { field, sampleValue: value ?? null }\n        fieldByName.set(field, descriptor)\n        fields.push(descriptor)\n      }\n\n      if (!hasSample.has(field) && value !== null && value !== undefined) {\n        descriptor.sampleValue = value\n        hasSample.add(field)\n      }\n    }\n  }\n\n  return fields\n}\n```\n\nThat helper preserves first-seen field order and returns `{ field, sampleValue }` for each field. `sampleValue` is the first non-nullish value seen for that field, or `null` if the field exists but only has nullish values in the captured data.\n\n## Local data\n\nStatic columns declared around `DynamicColumns` keep their positions even though the runtime columns are produced between them. This example pins `id` on the left and `actions` on the right, and lets the table fill in everything else from the first local result.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { DynamicColumns, localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, region: 'US' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, region: 'EU' },\n]\n\ntype Product = (typeof rows)[number]\n\nconst model = localModel<Product>({ data: rows })\n\nfunction labelFor(field: string) {\n  return field.replaceAll('_', ' ').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nfunction fieldsToShow(rows: readonly Product[]) {\n  const fields: string[] = []\n  const seen = new Set<string>()\n\n  for (const row of rows) {\n    for (const field of Object.keys(row)) {\n      if (field !== 'id' && !seen.has(field)) {\n        seen.add(field)\n        fields.push(field)\n      }\n    }\n  }\n\n  return fields\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 300 }}>\n      <DataTableColumn field=\"id\" sticky=\"left\">\n        <DataTableColumnHeader className=\"w-24\">SKU</DataTableColumnHeader>\n        <DataTableCell className=\"text-muted-foreground tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      <DynamicColumns<Product>>\n        {({ data }) =>\n          fieldsToShow(data).map((field) => (\n            <DataTableColumn field={field} key={field}>\n              <DataTableColumnHeader className=\"min-w-32\">{labelFor(field)}</DataTableColumnHeader>\n              <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n            </DataTableColumn>\n          ))\n        }\n      </DynamicColumns>\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"w-24\">Actions</DataTableColumnHeader>\n        <DataTableCell>{({ row }) => <button type=\"button\">Open {row.data.id}</button>}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe `key={field}` on each generated column matters. Column state such as width, order, visibility, and sticky placement is tied to the field identity.\n\n## Remote data\n\nRemote tables derive their schema from the first non-empty result the same way local tables do. The descriptor helper above is reusable — the `sampleValue` lets the render prop pick alignment per field:\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"id\" sticky=\"left\" />\n\n  <DynamicColumns<ProductRow>>\n    {({ data }) =>\n      fieldsFromRows(data, (field) => field !== 'id').map(({ field, sampleValue }) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>{labelFor(field)}</DataTableColumnHeader>\n          <DataTableCell {...(typeof sampleValue === 'number' ? { className: 'text-right tabular-nums' } : {})}>\n            {({ cellValue }) => String(cellValue ?? '')}\n          </DataTableCell>\n        </DataTableColumn>\n      ))\n    }\n  </DynamicColumns>\n\n  <DataTableColumn id=\"actions\" sticky=\"right\" />\n</DataTable>\n```\n\nThe headers appear after the first non-empty result resolves. Later pages or viewport fetches do not add more columns — that avoids mid-scroll layout changes, but it means the first captured result must contain every field you want to show.\n\nWhen the backend can return column metadata alongside the rows, prefer that. Metadata can render headers before any rows arrive, works for empty responses, and can carry labels, alignment, and visibility hints out of band.\n\n## Placeholders\n\nThe field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data. Placeholder-only fields show up in the discovered column list, and fields that exist only on real rows go missing if the first captured result is all placeholders.\n\nMake placeholders schema-compatible with real rows:\n\n```ts\nconst placeholder = {\n  id: '',\n  product: '',\n  category: '',\n  stock: null,\n}\n```\n\nDo not put placeholder-only fields in the visible schema. If a placeholder needs technical fields for rendering, filter them with `includeField`. If the real field list cannot be known until the backend responds, return column metadata from the backend or wait for a real data result instead of relying on placeholder shape.\n\n## Grouped data\n\nGrouped models mix group-header rows and body rows in the same array. The field scanner treats them the same way, so any field on a group-header row (a `groupLabel`, a level number, a count) would become a body column unless you filter group rows out first:\n\n```ts\nconst fields = fieldsFromRows(\n  data.filter((row) => !(typeof row === 'object' && row !== null && 'groupLabel' in row)),\n  (field) => field !== 'id'\n)\n```\n\nRender group labels with `DataTableGroupHeader`; keep runtime columns focused on ordinary body rows.\n\n## Rediscovering columns\n\n`DynamicColumns` is intentionally one-shot. A user action that changes row values should not reshuffle the table's columns.\n\nWhen a user intentionally switches to a different dataset shape, remount the table:\n\n```tsx\n<DataTable key={datasetId} model={model}>\n  <DynamicColumns>{/* declare columns from the new first result */}</DynamicColumns>\n</DataTable>\n```\n\nA user-facing column-management UI — letting the user add, remove, or reorder fields after mount — is a different problem. Keep the field list in your own React state and map it to `DataTableColumn` declarations directly; `DynamicColumns` doesn't help here, and trying to bend it to that shape leads to the temporal bugs the one-shot rule was designed to avoid.\n\n## When this does not fit\n\n- **Backend-defined schemas.** Return column metadata alongside the rows when headers must render before data, when valid responses can be empty, or when labels and alignment come from server configuration.\n- **Statically-known schemas.** Use a static field list when the schema is known in code but the JSX would be repetitive — see [Generated columns](/data-table/columns/defining-columns/#generated-columns).\n- **Runtime column-management UI.** Lift the field list into your own React state when the user can add, remove, or reorder fields after mount.\n\n`DynamicColumns` fits when the schema is data-defined, the first non-empty result is representative, and remounting the table is acceptable for re-discovery.\n","packages/data-table/docs/3.columns/09.column-layout.md":"---\ntitle: Column Layout\ndescription: Set base column widths and choose which columns grow into extra horizontal space.\nsidebar:\n  label: Column Layout\n---\n\nColumn width has two parts: the base width and the realized width.\n\nThe base width comes from the column header. The table measures `DataTableColumnHeader`, then uses that measured width as the starting point for both the header and every body cell in that column.\n\nThe realized width is the width the column gets after the table compares all base widths with the viewport. When the viewport is wider than the total base width, extra space is distributed by the table. When the viewport is narrower, columns keep their base widths and the table scrolls horizontally.\n\n## Set Base Widths On Headers\n\nUse header width classes for the minimum useful size of each column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror those widths on `DataTableCell`. Body cells render inside tracks sized by the table. Cell width utilities can fight those tracks and cause clipping or overlap.\n\n## Choose Which Columns Grow\n\nUse `DataTableColumn grow={number}` for columns that should absorb extra viewport width. The value is a ratio, not a CSS flex shorthand.\nOmitting `grow`, or passing `grow={0}`, keeps the column fixed when another visible column grows.\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"description\" grow={3}>\n  <DataTableColumnHeader className=\"min-w-80\">Description</DataTableColumnHeader>\n  <DataTableCell>{DescriptionCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"versions\">\n  <DataTableColumnHeader className=\"w-40\">Versions</DataTableColumnHeader>\n  <DataTableCell>{VersionBadgesCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn id=\"actions\">\n  <DataTableColumnHeader className=\"w-16 justify-center\">Actions</DataTableColumnHeader>\n  <DataTableCell>{ActionsCell}</DataTableCell>\n</DataTableColumn>\n```\n\nIn this layout, `name` and `description` receive all extra width. `description` receives three times as much extra width as `name`. `versions`, `updated`, and `actions` keep their base widths.\n\nChoose grow columns from the local data and workflow. IDs, slugs, checkboxes, action menus, short labels, badges, version counts, timestamps, and compact metadata usually should not grow. Names, titles, descriptions, summaries, messages, notes, paths, and other text-heavy content usually benefit from extra room. The field name is only a clue: a short code-like `name` may be fixed, while a user-authored label column may deserve room.\n\nDo not make every column grow. The point of `grow` is to let useful text columns breathe while compact columns stay compact.\n\n## What Happens Without Grow\n\nTables that do not set `grow` preserve the default auto-fill behavior: when the viewport is wider than the total base width, extra space is distributed evenly across visible columns. This keeps existing tables filling their container without changing every column declaration.\n\nOnce any visible column has a positive finite `grow` value, only grow columns receive leftover width. Non-grow columns keep their base widths.\n\n## There Is No Shrink Prop\n\n`grow` only distributes extra space. It does not shrink columns below their base widths, and there is no `shrink` table API.\n\nWhen the viewport narrows, grow space disappears first. After the total base width no longer fits, the table keeps base widths and uses horizontal scrolling.\n\n## Resizing Grow Columns\n\nColumn resizing sets the rendered width of that column. During and after a resize override, the table preserves the current rendered widths of the other columns instead of redistributing grow space on every pointer move. That keeps the resize handle under the pointer and lets wider columns push the table into horizontal overflow.\n\nClear the resize override to return the column to its measured base width and the normal `grow` distribution.\n\nPersist resize overrides as user-selected widths, not as automatic grow output. The saved width is the user's explicit override; automatic grow space is recalculated only when no resize override is active for that layout.\n","packages/data-table/docs/3.columns/index.md":"---\ntitle: Overview\ndescription: Define the visible columns, headers, and cells that turn model rows into a grid.\nsidebar:\n  order: 0\n  label: Overview\n---\n\nColumns decide which fields are visible, in what order, at what width, and how each cell is rendered. You declare them as JSX children of `DataTable` — one `DataTableColumn` per visible field, with its header and cell renderer inside.\n\nThe JSX reads like a static description of the table, but `DataTableColumn` and its children don't render in place. Each one registers a definition with the table, which then draws the virtualized header row and the visible body cells from those definitions. That indirection is what makes virtualization possible: a single cell renderer is reused across many row positions as the user scrolls, and headers compose cleanly with sticky pinning, resizing, slots, and visibility.\n\n## Basic columns\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium\">{row.data.name}</span>\n              <span className=\"text-xs text-muted-foreground\">{row.data.id}</span>\n            </div>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : 'Healthy'}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What each part defines\n\n- `DataTableColumn` — one column, keyed by `field`.\n- `DataTableColumnHeader` — header content and header class.\n- `DataTableCell` — body-cell renderer and cell class.\n\nOmit the header to fall back to the field name; omit the cell to fall back to the field value.\n\n`field` is both the cell lookup key (`row.data[field]`) and the column's persistent identity. Keep it stable across releases if you use any column features that save state — see [State Persistence](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the rules around renaming.\n\nFor layout, put base widths on `DataTableColumnHeader` and use `DataTableColumn grow={...}` only for columns that should absorb extra horizontal space. See [Column Layout](/data-table/columns/column-layout/) for the sizing model and guidance on choosing fixed vs growing columns from the data being displayed.\n\n## Where to go next\n\nDeclarative shape:\n\n- [Defining Columns](/data-table/columns/defining-columns/) — `field`, default rendering, declaration order, visibility, generated columns.\n- [Formatting Cells and Headers](/data-table/columns/cell-and-header-renderers/) — render params, extracted renderer functions, custom header content.\n- [Column Layout](/data-table/columns/column-layout/) — base widths, `grow` ratios, fixed metadata columns, and horizontal overflow.\n- [Column Groups](/data-table/columns/column-groups/) — wrap adjacent columns under a shared header.\n- [Sticky Columns](/data-table/columns/sticky-columns/) — pin a column to the left or right edge during horizontal scrolling.\n\nInteractive layout:\n\n- [Column Visibility](/data-table/columns/column-visibility/) — declarative defaults, runtime toggling, \"Columns\" picker.\n- [Column Resizing](/data-table/columns/column-resizing/) — drag handle in the header, presets, auto-fit.\n- [Column Reordering](/data-table/columns/column-reordering/) — drag-and-drop individual columns or whole groups.\n\nDynamic schemas:\n\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","packages/data-table/docs/4.grouped-rows.md":"---\ntitle: Grouped Rows\ndescription: Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.\nsidebar:\n  label: Grouped Rows\n---\n\nWhen records belong to natural sections — products by category, invoices by customer, employees by department — group headers split the scrolling row stream into visible groups. Users see one continuous list; the headers anchor to the top of each section as new rows scroll into view.\n\n(This page is about row-level grouping. For grouping in the column header row, see [column groups](/data-table/columns/column-groups/).)\n\n## Letting users change groups\n\nThe common case: a \"Group by\" control where the user picks the grouping at runtime. Click the buttons in this example to regroup the table:\n\n```tsx live\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ninterface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  status: 'In stock' | 'Low stock'\n}\n\ninterface ProductGroup {\n  label: string\n}\n\ntype GroupBy = 'none' | 'category' | 'status'\n\nconst products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', status: 'In stock' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office', status: 'Low stock' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-004', name: 'Mechanical Keyboard', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-005', name: 'Studio Monitor', category: 'Audio', status: 'Low stock' },\n  { id: 'SKU-006', name: 'Audio Interface', category: 'Audio', status: 'In stock' },\n]\n\nfunction isProduct(row: Product | ProductGroup): row is Product {\n  return 'id' in row\n}\n\nfunction rowKey(row: Product | ProductGroup) {\n  return isProduct(row) ? row.id : `group-${row.label}`\n}\n\nfunction groupProducts(rows: Product[], groupBy: GroupBy) {\n  if (groupBy === 'none') {\n    return rows\n  }\n\n  const buckets = new Map<string, Product[]>()\n  for (const row of rows) {\n    const key = groupBy === 'category' ? row.category : row.status\n    const bucket = buckets.get(key)\n    if (bucket) {\n      bucket.push(row)\n    } else {\n      buckets.set(key, [row])\n    }\n  }\n\n  const data: (Product | ProductGroup)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const [key, items] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${key} (${items.length})` })\n    data.push(...items)\n  }\n\n  return { data, groups }\n}\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = (actionState?.group?.payload ?? 'category') as GroupBy\n\n  const options: { value: GroupBy; label: string }[] = [\n    { value: 'none', label: 'No groups' },\n    { value: 'category', label: 'Group by category' },\n    { value: 'status', label: 'Group by status' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <button\n          aria-pressed={groupBy === option.value}\n          className={`rounded-md border px-3 py-1 text-sm ${groupBy === option.value ? 'bg-foreground text-background' : 'bg-background'}`}\n          key={option.value}\n          onClick={() => dispatch({ action: 'group', payload: option.value })}\n        >\n          {option.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['group'],\n      initialActions: [{ action: 'group', payload: 'category' }],\n      actions: {\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <GroupByControls engineRef={engineRef} />\n      <DataTable\n        className=\"rounded-xl\"\n        computeRowKey={({ data }) => rowKey(data as Product | ProductGroup)}\n        engineRef={engineRef}\n        model={model}\n        style={{ height: 320 }}\n      >\n        <GroupHeaderCell className=\"bg-muted/60 px-3 py-2 text-sm font-medium\">\n          {({ row }) => String((row.data as ProductGroup).label)}\n        </GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nThe interactive grouping above does three things:\n\n- **Declares a `group` pipeline stage** on the model so the table knows where in its data pipeline regrouping runs. The pipeline lists the stages in order; `['group']` is the simplest case, but `['filter', 'sort', 'group']` is the typical production shape.\n- **Defines a `group` action** whose handler receives the current data and a payload (the user's selection) and returns the new `{ data, groups }` shape.\n- **Connects the controls through the table engine.** The buttons publish to `dispatchModelAction$`; the active button reads `modelActionState$`. The toolbar never stores a second copy of the grouping state.\n\nThe wire-up, isolated from the demo's rendering code, looks like this:\n\n```tsx\nconst [model] = useState(() =>\n  localModel<Product, ProductGroup>({\n    data: products,\n    pipeline: ['group'],\n    initialActions: [{ action: 'group', payload: 'category' }],\n    actions: {\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n      },\n    },\n  })\n)\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = actionState?.group?.payload ?? 'category'\n\n  return (\n    <button aria-pressed={groupBy === 'category'} onClick={() => dispatch({ action: 'group', payload: 'category' })}>\n      Group by category\n    </button>\n  )\n}\n```\n\nFor the full filter + sort + group combination — three controls sharing one pipeline — see [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for the runnable end-to-end recipe.\n\n## Pre-grouped data\n\nWhen the grouping is fixed at mount and won't change at runtime, skip the pipeline action and pass the already-flattened result directly:\n\n```tsx\nconst tableData = {\n  data: [\n    { label: 'Office (2)' },\n    { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n    { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  ],\n  groups: [{ index: 0, level: 0 }],\n}\n\nconst model = localModel(tableData)\n```\n\n`GroupHeaderCell` receives `row.data` for the marked entry, so it can read whatever fields you put on the group object (here, `label`).\n\n## How grouping is expressed\n\nThe table doesn't accept nested children. It receives one flattened row array plus markers identifying which entries are group headers:\n\n```ts\ntype GroupedRows<TData, TGroup> = {\n  data: Array<TData | TGroup>\n  groups: { index: number; level: number }[]\n}\n```\n\n- `data` mixes group rows and data rows in display order.\n- `groups[]` marks which positions in `data` are headers. Each marker carries an `index` (zero-based position in the flattened array) and a `level` (nesting depth).\n- The table renders marked entries through `GroupHeaderCell` instead of through column cells.\n\nGroup rows only need the fields your `GroupHeaderCell` reads. Data rows need the fields your columns read. The two shapes can differ; the table picks the renderer per entry based on the markers.\n\n## Multi-level grouping\n\nFor deeper hierarchy (region → category → product), use sequential markers with increasing levels:\n\n```ts\nconst groups = [\n  { index: 0, level: 0 }, // 'North America'\n  { index: 1, level: 1 }, //   'Office'\n  { index: 4, level: 1 }, //   'Peripherals'\n  { index: 7, level: 0 }, // 'Europe'\n  { index: 8, level: 1 }, //   'Office'\n]\n```\n\nHierarchy is implicit in row order: every level-1 marker belongs to the most recent level-0 marker that precedes it. Style nesting visually by branching on `row.level` inside `GroupHeaderCell`:\n\n```tsx\n<GroupHeaderCell>\n  {({ row }) => (\n    <div className={row.level === 0 ? 'bg-muted font-medium' : 'bg-muted/40 pl-6'}>{(row.data as { label: string }).label}</div>\n  )}\n</GroupHeaderCell>\n```\n\n`level` controls visual and sticky nesting only — it does not add parent-child data behavior. If you need to know which level-0 group a row belongs to, derive it from row order at the time you flatten.\n\n## Remote grouped data\n\nWhen the grouping is computed on the backend, the fetch result includes markers alongside the rows; `remoteModel` forwards them to the table. The backend returns a flattened grouped result, not nested children:\n\n```ts\nasync function fetchProducts(params: FetchParams<Query>) {\n  const result = await fetchGroupedRows(params)\n\n  return {\n    rows: result.rows,\n    groups: result.groups,\n    totalCount: result.totalCount,\n  }\n}\n```\n\nIn offset mode, `totalCount` is the length of the full flattened result (including group headers), and marker indexes are positions in that full result — not relative to the current page. In append mode, return markers for the row stream after each append. Group rows still need a shape your `GroupHeaderCell` can branch on.\n\n## Rules of thumb\n\n- Group rows are virtualized full-width rows — keep their content lightweight.\n- Give group rows stable keys via `computeRowKey` when grouping, sorting, or filtering can reorder them. Use a stable group identifier (`groupKey`, `region`, etc.), not the current group index.\n- The table doesn't compute groups for you. Either group your data before passing it in, or compute inside a pipeline stage.\n- `level` controls visual and sticky nesting. It does not add parent-child data behavior.\n\n## When this doesn't fit\n\n- **Nested source data.** Flatten it before passing to the table — the table's display contract is flat-with-markers, not nested children.\n- **User-toggleable expand/collapse.** Keep the expanded/collapsed state in your app and rebuild the flattened `data` and `groups` from it on each toggle.\n- **Remote grouping across unloaded ranges.** Compute the grouping on the backend, or return enough metadata for the client to place group headers correctly without seeing every row.\n\nSee [Group Rows by Region and Category](/data-table/examples/grouped-data/) for a complete pre-grouped local table, and [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for grouping wired alongside filter and sort.\n","packages/data-table/docs/5.state-persistence.md":"---\ntitle: State Persistence\ndescription: Persist table feature state and model action state with opt-in adapters.\nsidebar:\n  label: State Persistence\n---\n\n`DataTableStatePersistence` saves column width, order, visibility, and model action state under a storage key, restoring them on mount. With it wired up, a user's resize, hide, drag, or filter survives a reload. Two requirements: mount the component inside the table, and pass an adapter for each feature you want persisted.\n\n## Minimal setup\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columns$, localModel, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport {\n  columnVisibilityPersistenceAdapter,\n  columnVisibilityState$,\n  setColumnVisibility$,\n} from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence } from '@virtuoso.dev/data-table/state-persistence'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n  { id: 'SKU-004', product: 'Noise-canceling Headset', category: 'Audio', stock: 9, internalId: 'inv-004' },\n]\n\nconst model = localModel({ data: rows })\nconst adapters = [columnVisibilityPersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nfunction ColumnPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div className=\"mb-3 flex flex-wrap gap-2 rounded-md border bg-muted/30 p-3 text-sm\">\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key) ?? column.visible !== false\n        return (\n          <button\n            aria-pressed={visible}\n            className={visible ? 'rounded border bg-background px-2 py-1 font-medium' : 'rounded border px-2 py-1 text-muted-foreground'}\n            key={key}\n            onClick={() => setColumnVisibility({ key, visible: !visible })}\n            type=\"button\"\n          >\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 360 }}>\n      <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-feature-page\" />\n      <ColumnPicker />\n\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"internalId\" visible={false}>\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Internal ID'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nReload after hiding a column or resizing a header — the adapters you passed restore that state.\n\n## Adapters\n\n| Adapter                                | Subpath              | Persists                                                                                 |\n| -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |\n| `modelStatePersistenceAdapter()`       | `/state-persistence` | Action payloads from `localModel()` / `remoteModel()` (filters, sort, group)             |\n| `columnOrderPersistenceAdapter()`      | `/column-reorder`    | Column order, keyed by field                                                             |\n| `columnWidthPersistenceAdapter()`      | `/column-resize`     | Width overrides, keyed by field                                                          |\n| `columnVisibilityPersistenceAdapter()` | `/column-visibility` | Visibility overrides (only those differing from the declarative default), keyed by field |\n\n```tsx\nimport { columnOrderPersistenceAdapter } from '@virtuoso.dev/data-table/column-reorder'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { columnVisibilityPersistenceAdapter } from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nconst adapters = [\n  columnVisibilityPersistenceAdapter(),\n  columnOrderPersistenceAdapter(),\n  columnWidthPersistenceAdapter(),\n  modelStatePersistenceAdapter(),\n]\n\n<DataTable model={model} style={{ height: 420 }}>\n  <DataTableStatePersistence adapters={adapters} storageKey=\"inventory-table\" />\n  {/* columns */}\n</DataTable>\n```\n\nStorage defaults to `window.localStorage`. Pass `storage` for `sessionStorage`, a test double, or an application-specific backend.\n\nThe `adapters` array must be stable — define it outside the component or wrap it in `useMemo()` so the persistence component doesn't resubscribe on every render.\n\n## Storage keys and resets\n\n`storageKey` identifies one logical table. Change it when the saved state should no longer apply (different product surface, breaking schema change).\n\n`resetKey` clears saved state without changing the identity — bump it when the user explicitly resets a view.\n\n```tsx\n<DataTableStatePersistence adapters={adapters} resetKey={resetVersion} storageKey=\"inventory-table\" />\n```\n\nAdapters preserve unmatched entries where they can, so state survives partially-matching dynamic schemas.\n\n## Field names are the persistence contract\n\nThe adapters above all key their saved state by `field`. A column's width entry is \"the width for the column whose `field` is `'price'`\", not \"the width for the column in position 2\". Same for order, visibility, and sticky pin. That's what makes saved state survive schema migrations, partial column lists, and dynamic column generation — the field name is the contract between the column declaration and the saved entry.\n\nTwo consequences fall out of that:\n\n**Don't rename `field` lightly.** A column declared as `field=\"price\"` last week and `field=\"cost\"` this week is two different columns from persistence's perspective. The width entry under `'price'` doesn't transfer to `'cost'`, and the user's saved width silently goes back to the default. For schema migrations that have to rename, plan a one-time migration step that rewrites saved entries from the old key to the new one.\n\n**Keep `field` separate from header copy.** Header text is presentation and changes freely (locale switches, A/B tests, restyling). `field` is identity and must stay the same:\n\n```tsx\n<DataTableColumn field=\"customerName\">\n  <DataTableColumnHeader>{locale === 'de' ? 'Kunde' : 'Customer'}</DataTableColumnHeader>\n</DataTableColumn>\n```\n\nThis separation is why persisted state survives locale changes — the saved width sits under `'customerName'` regardless of what the header reads in any given language.\n\n## Model action persistence\n\nOnly actions marked with `persistence` are saved. Add it to a pipeline action for local models, or to an action that transforms request params for remote models.\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { status: 'all' },\n  actions: {\n    filter: {\n      persistence: { isEmpty: (value) => value === 'all', key: 'status' },\n      handler: ({ params, payload }) => ({ ...params, status: payload }),\n    },\n  },\n})\n```\n\nThe adapter saves action payloads, not row data. On restore, the model replays the action and rebuilds the request or local pipeline result.\n\nSee the [State Persistence example](/data-table/examples/state-persistence/) for a remote table wiring model and column-width persistence together.\n","packages/data-table/docs/6.controlling-the-table.md":"---\ntitle: Controlling the Table\ndescription: Read table state and publish table actions from nearby toolbars, panels, and other application UI.\nsidebar:\n  label: Controlling the Table\n---\n\nExternal UI — toolbars, settings menus, status badges — often needs to read the table's state (current scroll position, visible columns, loading phase) and trigger its actions (scroll to a row, toggle a column, refresh data). The table exposes both through its internal reactive runtime; external UI taps that runtime directly instead of receiving lifted state through props.\n\nTwo pieces wire the connection. The first is an identifier for the table instance: `useEngineRef()` returns a ref for UI in the same component, and a string `engineId` reaches a table mounted elsewhere in the tree. The second is the channels the identifier carries — _state cells_ like `scrollLocation$` and `columns$` expose values you read with `useRemoteCellValue`, and _action streams_ like `scrollToRow$` and `setColumnVisibility$` accept commands you publish with `useRemotePublisher`. The `$` suffix marks both flavors.\n\n## Engine refs\n\nReach for `engineRef` when the external UI lives in the same component as the table — a toolbar two divs up, a status panel rendered as a sibling. `useEngineRef()` returns an opaque ref; pass it to `<DataTable engineRef={engineRef}>` and to the remote hooks beside it, and they resolve to the same engine.\n\n```tsx live\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, scrollLocation$, scrollToRow$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 80, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 81\n        </Button>\n      </div>\n\n      <div className=\"rounded-lg border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n        {scrollLocation === undefined ? 'Waiting for scroll state...' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 360 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nMount order doesn't matter. The hooks register against the ref immediately and start receiving values as soon as the table's engine comes up.\n\n## Engine IDs\n\nReach for `engineId` when the external UI lives _far_ from the table — a page-level toolbar in the layout shell, a sibling panel that doesn't share a parent component with the table, anything where threading a ref through props would mean prop-drilling through unrelated levels. Pick a stable string, pass it as `<DataTable engineId={…}>`, and hand the same string to the hooks anywhere in the tree.\n\n```tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { scrollToRow$, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst TABLE_ENGINE_ID = 'inventory-table'\n\nfunction PageToolbar() {\n  const scrollToRow = useRemotePublisher(scrollToRow$, TABLE_ENGINE_ID)\n\n  return (\n    <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n      Top\n    </Button>\n  )\n}\n\nfunction InventoryTable({ rows }) {\n  return (\n    <DataTable model={model} engineId={TABLE_ENGINE_ID} style={{ height: 420 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nUse a unique ID per table on a given screen (`inventory-table`, `transactions-table`). The registry holds one engine per ID, so a second table mounting with the same `engineId` silently overwrites the first — and when the first unmounts, the registry entry is cleared out from under the second. The symptoms are confusing; the prevention is trivial.\n\n## Firing model actions from external UI\n\nFiltering, sorting, grouping, and search often live in a toolbar beside the table. Keep that toolbar connected to the model through the table engine: publish the user action with `dispatchModelAction$`, then read `modelActionState$` to decide which control is active. The toolbar does not need the model handle, and it does not need to keep a separate React state value in sync with the model.\n\n```tsx live\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ntype StatusFilter = 'all' | 'active' | 'paused'\n\nconst rows = [\n  { id: 'acct-1', account: 'Northwind', status: 'active' },\n  { id: 'acct-2', account: 'Contoso', status: 'paused' },\n  { id: 'acct-3', account: 'Fabrikam', status: 'active' },\n]\n\nfunction filterRows(data: typeof rows, status: StatusFilter) {\n  return status === 'all' ? data : data.filter((row) => row.status === status)\n}\n\nfunction StatusControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const selected = (actionState?.status?.payload ?? 'all') as StatusFilter\n\n  const options: { value: StatusFilter; label: string }[] = [\n    { value: 'all', label: 'All' },\n    { value: 'active', label: 'Active' },\n    { value: 'paused', label: 'Paused' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <Button\n          aria-pressed={selected === option.value}\n          key={option.value}\n          onClick={() => dispatch({ action: 'status', payload: option.value })}\n          variant={selected === option.value ? 'default' : 'outline'}\n        >\n          {option.label}\n        </Button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel({\n      data: rows,\n      pipeline: ['status'],\n      initialActions: [{ action: 'status', payload: 'all' }],\n      actions: {\n        status: {\n          stage: 'status',\n          handler: ({ data, payload }) => filterRows(data, payload as StatusFilter),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <StatusControls engineRef={engineRef} />\n      <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} engineRef={engineRef} model={model} style={{ height: 280 }}>\n        <DataTableColumn field=\"account\">\n          <DataTableColumnHeader>Account</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n`modelActionState$` tracks stateful model actions: the current filter, sort, group, search, or other action payload that represents a user choice. Command-like actions are different. `refresh`, `cancel`, `loadMore`, source mutators, and similar commands can be dispatched through `dispatchModelAction$`, but they are not remembered in `modelActionState$` because they do not describe the current toolbar state.\n\nThe bridge blocks only model lifecycle names owned by the table itself: `handshake`, `disconnect`, and `viewportChange`.\n\n## Common state and actions\n\nThe cells and streams most external UI reaches for, grouped by the hook they pair with.\n\nRead with `useRemoteCellValue`:\n\n- `columns$`, `visibleColumns$`, `columnWidths$` — column shape and layout\n- `scrollLocation$`, `scrollerElement$`, `itemHeight$` — scroll state and DOM references\n\nPublish with `useRemotePublisher`:\n\n- `scrollToRow$`, `scrollIntoView$`, `cancelSmoothScroll$` — scroll commands\n- `setColumnSticky$` — pin a column at runtime\n\nFeature modules contribute their own: `resizeColumn$` and friends from `@virtuoso.dev/data-table/column-resize`, `reorderColumns$` from `column-reorder`, `setColumnVisibility$` from `column-visibility`. Each feature page documents the cells and streams it adds.\n\nInside the table — in a cell renderer, in a custom row component — use the local hooks instead: `useVirtuosoLocation()`, `useCurrentlyRenderedData()`, `useVirtuosoLoadingState()`. They read the same underlying state but resolve the engine from React context, so no ref or ID is needed.\n\nFor DOM-level integrations, prefer `scrollerElement$` and `itemHeight$` over `document.querySelector`. The cells stay in sync with React's mount and unmount; a query selector can return a node from a torn-down render or miss a remount entirely.\n\n## Scroll lifecycle props\n\nOne-way notifications and first-render positioning live on the component, not the engine:\n\n- `initialLocation` — scrolls to a row when the table first renders with data\n- `onScroll` — receives the current `ListScrollLocation`\n- `onRenderedDataChange` — receives the rendered data rows, excluding group rows\n\n```tsx\n<DataTable\n  model={model}\n  initialLocation={{ index: 80, align: 'center' }}\n  onRenderedDataChange={(renderedRows) => {\n    console.log(renderedRows.map((row) => row.id))\n  }}\n  onScroll={(location) => {\n    console.log(location.scrollTop)\n  }}\n>\n  {/* columns */}\n</DataTable>\n```\n\nUse props when the parent only needs to observe — log scroll position, fire analytics, set an initial location once. Reach for the remote hooks when UI elsewhere in the tree needs to subscribe to the same updates or push commands back; props can't carry a subscription out to a sibling component.\n\n## Overscan controls\n\nA pair of static-tuning props that, like the scroll lifecycle props above, live on the component rather than on the engine — overscan is a per-table policy, not something external UI flips at runtime. The defaults are good for typical row heights and cell complexity; raise them only when you can see the table struggling.\n\n- `increaseViewportBy` — extra vertical pixels rendered before and after the viewport. Raise when rows contain images that need to warm up before they scroll into view, or when each row's render cost is high enough that the user can outrun the scheduler.\n- `columnOverscanCount` — extra columns rendered on each side of the horizontal viewport. Raise on wide tables that feel jumpy during horizontal scroll, especially when cells contain charts or non-trivial markup.\n\nBoth numbers cost render work even when the user isn't scrolling, so bump them in small steps and stop as soon as the symptom is gone.\n","packages/data-table/docs/7.customization/01.styling.md":"---\ntitle: Styling Cells, Headers, and Rows\ndescription: Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.\nsidebar:\n  label: Styling\n---\n\nThe visible parts of the table — cells, headers, the outer frame — all accept Tailwind classes the way any component in your app does. Reach for column-level styling when a single column needs to look different (a status pill, a right-aligned number, a muted secondary line) and wrapper-level styling when the change applies to the whole table.\n\n## Column-level styling\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', status: 'Launch ready', theme: 'Office' },\n  { name: 'USB-C Dock', status: 'Low stock', theme: 'Peripherals' },\n  { name: 'Noise-canceling Headset', status: 'Backorder', theme: 'Audio' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"theme\">\n        <DataTableColumnHeader>Theme</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-slate-100 px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => (\n            <span\n              className={\n                cellValue === 'Backorder'\n                  ? 'font-medium text-red-600'\n                  : cellValue === 'Low stock'\n                    ? 'font-medium text-amber-600'\n                    : 'font-medium text-emerald-600'\n              }\n            >\n              {String(cellValue)}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n`className` on `DataTableCell` styles the positioned cell. A column header has two widths, so it exposes two styling surfaces:\n\n- `DataTableColumnHeader.className` styles the intrinsic header content and sets the column's base width.\n- `DataTableColumnHeader.containerProps` styles the full rendered column width and accepts event handlers and ARIA attributes.\n\nThe table measures the intrinsic surface before it distributes spare width through `grow`. Measuring the rendered surface instead would feed the grown width back in as the next base width, so the column could not shrink with its viewport. `containerProps` provides a separate full-width paint and interaction surface without changing that measurement.\n\nUse `containerProps` when a background, hover state, outline, box shadow, cursor, or click target must cover the spare width:\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-64\" containerProps={{ className: 'cursor-pointer hover:bg-muted/50' }}>\n    Product\n  </DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nKeep layout on `className`. The table owns the outer container's geometry, so width, flex, margin, padding, border, display, and `justify-*` styles on `containerProps` can clip content or move edge-aligned slots. Use `box-shadow` or `outline` instead of a border on that surface, and use `HeaderEnd` for controls that must align to the rendered edge.\n\nContainer events receive bubbled events from nested controls and header slots. If the header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nRender-function children still let you wrap only the value when it needs its own markup, such as a badge or two-line label.\n\n## Wrapper-level styling\n\n`className` on `<DataTable>` reaches the outer frame around the scroll viewport. The shadcn wrapper already provides the default frame (`rounded-md border`) and table surface, so you do not need to repeat `rounded-md border` at each instance. Use wrapper classes for intentional frame overrides, shadows, and table-level CSS variable overrides.\n\n```tsx\n<DataTable className=\"rounded-xl shadow-sm\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nTo remove or strengthen the default frame, override it directly:\n\n```tsx\n<DataTable className=\"border-0\" model={model}>\n  {/* columns */}\n</DataTable>\n\n<DataTable className=\"border-2\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nFor pixel sizing, prefer `style` (`style={{ height: 360 }}`) — the table needs a measurable height to virtualize, and Tailwind's `h-*` utilities work too, as long as the resolved height is real (not a percentage of an unmeasured parent).\n\n## What this page doesn't cover\n\nTwo adjacent concerns have their own pages:\n\n- **Replacing the table's structural components** (Row, GroupHeader, sticky wrappers, scroll element) lives in [Replacing Internals](/data-table/customization/replacing-internals/). That's the `components` prop pattern.\n- **Default classes baked into the shadcn wrapper** — change those by editing `@/components/ui/data-table` directly. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/) for the wrapper anatomy.\n","packages/data-table/docs/7.customization/02.replacing-internals.md":"---\ntitle: Replacing Internals\ndescription: Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.\nsidebar:\n  label: Replacing Internals\n---\n\nMost styling you do to a table is column-level — a class on a cell, a renderer that builds a status pill. But some changes apply across every row at once: hover states, density, dividers, the markup around sticky columns, the element that actually scrolls. Those changes live in the table's structural slots, and you change them by passing a replacement component.\n\nThe `components` prop is the channel. Each key in `components` swaps out one piece of the table's internal markup with your own component.\n\n```tsx\nimport * as React from 'react'\nimport type { RowComponentProps } from '@virtuoso.dev/data-table'\n\nconst components = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b transition-colors hover:bg-muted/50\" {...props} />\n  }),\n}\n\n;<DataTable components={components} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nThe override receives the same props the default `<div>` would have received — class, style, children, data attributes, the table's `context` value. Forward them all, or the table loses track of the element. The forwarded `ref` is what the virtualizer reads sizes from, so passing it to the root element is mandatory; the table will appear broken without it.\n\n## What you can replace\n\nThe `components` prop covers structural slots that appear repeatedly or wrap whole regions:\n\n- `Row` — the element used for data rows (one per visible row). Hover states, dividers, alternating backgrounds, density, row-level transitions.\n- `StickyHeader` — the wrapper around the sticky header row. Backgrounds and borders that need to match your page chrome instead of the default.\n- `StickyColumnContainer` — the wrapper around each sticky column region, used in both header and body. The `data-sticky` attribute on the props tells you whether it's the left or right side.\n- `LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter` — the three loading-state slots. Covered separately in [Empty and Loading States](/data-table/customization/empty-and-loading-states/) because each one has its own narrative.\n\n```tsx\nimport type { DataTableComponents, RowComponentProps, StickyColumnContainerComponentProps } from '@virtuoso.dev/data-table'\n\nconst components: DataTableComponents = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b\" {...props} />\n  }),\n  StickyColumnContainer: React.forwardRef<HTMLDivElement, StickyColumnContainerComponentProps>(function Sticky(props, ref) {\n    const side = props['data-sticky']\n    return <div ref={ref} className={side === 'left' ? 'bg-card shadow-[1px_0_0_0_hsl(var(--border))]' : 'bg-card'} {...props} />\n  }),\n}\n```\n\n## Two slots that aren't in `components`\n\nA few replacements have their own top-level props instead of living under `components`:\n\n- `EmptyPlaceholder` — rendered when the model has zero rows. Top-level prop because most apps customize it and want a single explicit place to look.\n- `ScrollElement` — the element that actually scrolls. Top-level prop because it interacts with viewport ownership (`useWindowScroll`, `customScrollParent`) — see [Scroll Containers](/data-table/customization/scroll-containers/).\n\nBoth follow the same forward-the-props rule.\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n;<DataTable EmptyPlaceholder={EmptyPlaceholder} ScrollElement={ScrollElement} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## When to edit the wrapper instead\n\nThe shadcn wrapper at `@/components/ui/data-table` already supplies defaults for every slot. Two questions decide where your override belongs:\n\n- **Does every table in your app want this change?** Edit the default in the wrapper file. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/).\n- **Does this one table want something different?** Pass `components` (or `EmptyPlaceholder` / `ScrollElement`) on that table only.\n\nBoth routes hit the same slots; the difference is scope.\n\n## Don't reach for `data-testid` as a styling hook\n\nPer the data-table conventions, `data-testid` is reserved for tests. When your override needs a styling hook, use a semantic data attribute (`data-density=\"compact\"`) or a className — not a test ID. The table itself never styles against `data-testid` and you shouldn't either.\n","packages/data-table/docs/7.customization/03.empty-and-loading-states.md":"---\ntitle: Empty, Loading, and Error States\ndescription: Customize empty placeholders and remote loading states without changing data model.\nsidebar:\n  label: Empty and Loading States\n---\n\nThe table has three \"no rows\" states: it has zero rows to display (empty), it's loading rows that haven't arrived yet (loading), or a load failed (error). Each state has its own slot component, and the shadcn wrapper ships sensible defaults for all of them — override a slot when the default copy doesn't fit your domain.\n\n## Empty placeholder\n\n`EmptyPlaceholder` renders when the model has zero rows. The component receives the table `context` prop.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel<{ name: string }>({ data: [] })\n\nfunction EmptyState({ context }: { context: { query: string } }) {\n  return <div className=\"p-6 text-sm text-muted-foreground\">No products match \"{context.query}\".</div>\n}\n\nexport default function App() {\n  return (\n    <DataTable EmptyPlaceholder={EmptyState} className=\"rounded-xl\" context={{ query: 'archived' }} model={model} style={{ height: 240 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Remote loading and error state\n\n`remoteModel()` drives three slot components:\n\n- `LoadingPlaceholder` — `initial` phase (first unresolved dataset)\n- `LoadingOverlay` — `refresh` phase (request params changed, old rows still visible)\n- `LoadingFooter` — `end` phase (append mode is fetching more rows below)\n\nOffset viewport fetches show up as placeholder rows, not a table-level loading slot. `DataTableLoadingState` exposes a `start` segment for custom integrations, but the built-in `remoteModel()` never sets it.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }: LoadingComponentProps) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nThe shadcn wrapper ships defaults for all three slots. Override them for different copy, skeletons, or error recovery controls.\n\nExternal UI reads the same state through `useVirtuosoLoadingState()` (inside the table) or `loadingState$` with remote engine hooks (outside).\n\n## Error recovery\n\nErrors attach to the phase that failed, so a first-load failure can be handled differently from a refresh failure:\n\n```tsx\nfunction LoadingPlaceholder({ loadingState }: LoadingComponentProps) {\n  if (loadingState.initial.status === 'error') {\n    return (\n      <div className=\"p-6 text-sm\" role=\"status\">\n        Could not load products. Check your connection and try again.\n      </div>\n    )\n  }\n\n  return <div className=\"p-6 text-sm text-muted-foreground\">Loading products...</div>\n}\n\nfunction LoadingFooter({ loadingState }: LoadingComponentProps) {\n  if (loadingState.end.status === 'error') {\n    return <div role=\"status\">Could not load more rows.</div>\n  }\n\n  return loadingState.end.status === 'loading' ? <div>Loading more...</div> : null\n}\n\n;<DataTable components={{ LoadingFooter, LoadingPlaceholder }} model={model} />\n```\n\nRetry buttons belong with the slot that's showing the error: initial failure → retry from the placeholder, refresh failure → retry from the overlay, append failure → retry from the footer.\n","packages/data-table/docs/7.customization/04.scroll-containers.md":"---\ntitle: Scroll Containers\ndescription: Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.\nsidebar:\n  label: Scroll Containers\n---\n\nThe default table renders its own scrolling element. Three props change that — pick one when the table shares a viewport with surrounding page chrome, or when virtualization needs to be driven by a different scrolling element.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 80 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\n  return (\n    <div ref={setScrollParent} className=\"h-[360px] overflow-auto rounded-xl border\">\n      <div className=\"sticky top-0 z-10 border-b bg-background px-3 py-2 text-sm font-medium\">Inventory workspace</div>\n      <DataTable computeRowKey={({ data }) => data.id} customScrollParent={scrollParent} increaseViewportBy={360} model={model}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Fill available panel height\n\nFor app pages where the table should consume the remaining space below fixed page chrome, keep the default internal table scroller and give the table a measured flex slot. This is usually better than guessing a pixel height.\n\nThe important pieces are:\n\n- a parent with a real height, such as a route shell, panel, or card body\n- `min-h-0` on each flex ancestor between that measured parent and the table\n- `shrink-0` on controls above the table\n- `flex-1 min-h-0` on `DataTable`, plus `style={{ height: '100%' }}`\n\n```tsx\nexport function OrdersPage() {\n  return (\n    <section className=\"flex h-full min-h-0 flex-col\">\n      <PageHeader className=\"shrink-0\" />\n\n      <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n        <Toolbar className=\"shrink-0\" />\n\n        <DataTable className=\"min-h-0 flex-1\" computeRowKey={({ data }) => data.id} model={model} style={{ height: '100%' }}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Order</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </section>\n  )\n}\n```\n\nUse this pattern only when the parent is height-constrained. If the table lives in normal document flow, keep a fixed table height (`style={{ height: 360 }}`) or intentionally switch to `useWindowScroll` / `customScrollParent`.\n\n## Window scrolling\n\n`useWindowScroll` makes the document viewport drive the table's scroll state:\n\n```tsx\n<DataTable model={model} useWindowScroll>\n  {/* columns */}\n</DataTable>\n```\n\nThe table no longer renders an internal scroller; its height comes from rendered content and the surrounding page layout. Sticky headers now interact with document-level sticky offsets, so test them against your actual header/nav stack.\n\n## Existing parent element\n\n`customScrollParent` attaches the table to an existing scrolling element:\n\n```tsx\nconst [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\nreturn (\n  <div ref={setScrollParent} className=\"h-[480px] overflow-auto\">\n    <Toolbar />\n    <DataTable customScrollParent={scrollParent} model={model}>\n      {/* columns */}\n    </DataTable>\n  </div>\n)\n```\n\n## Custom scroll element\n\n`ScrollElement` replaces the default scroller component while keeping the rest of the table contained:\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n<DataTable ScrollElement={ScrollElement} model={model} />\n```\n\nPick one scroll mode per table. For `customScrollParent`, make sure that element has a real height and is the element that actually scrolls.\n","packages/data-table/docs/7.customization/05.ambient-context.md":"---\ntitle: Ambient Context\ndescription: A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.\nsidebar:\n  label: Ambient Context\n---\n\nThe `context` prop is the table's _ambient context_: a single bag of values that's available throughout the table's customizable parts — `EmptyPlaceholder`, the loading components, `ScrollElement`, `computeRowKey`, structural component overrides. You set it once on `<DataTable>` and the table passes it to every callback and component override that reads it, without per-callback prop wiring.\n\nTypical contents are values the surrounding screen owns but the table's slots need to read:\n\n- the current search query, so `EmptyPlaceholder` can render \"no results for _archived_\"\n- the active tenant ID, so `computeRowKey` can scope keys across saved views\n- a sticky-header offset the parent layout controls, so a custom `ScrollElement` can honor it\n\n```tsx\nfunction EmptyPlaceholder({ context }) {\n  return <div>{context.emptyMessage}</div>\n}\n\n;<DataTable\n  EmptyPlaceholder={EmptyPlaceholder}\n  computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`}\n  context={{ emptyMessage: 'No archived products', tenantId: 'acme' }}\n  model={model}\n>\n  {/* columns */}\n</DataTable>\n```\n\nCompared to wiring each callback to its own props or closing over surrounding state, `context` is the right channel when several internals consume the same value (a search query the empty state needs to display _and_ the loading state references) or when the value comes from above the table component (a router param, an auth scope, a feature flag).\n\n## What receives context\n\nThe same `context` value reaches:\n\n- [`computeRowKey`](/data-table/data-model/row-keys/) — for scoping row keys across tenants, saved views, or table instances that might share row IDs.\n- `EmptyPlaceholder` — for the failed-search query, the filter that returned zero rows, or recovery copy.\n- Loading slots (`LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter`) — for status copy or retry handlers that depend on screen state.\n- Row component overrides — for density flags, alternating-row styling toggles, hover-state preferences.\n- Sticky wrapper overrides — for sticky background colors that match the surrounding chrome.\n- Custom scroll elements — for layout flags, scrollbar styling, or whether to honor a sticky header height.\n\n## What doesn't receive context\n\nCell and header renderers don't receive `context`. They run inside the table's column-virtualization loop, where threading context through every cell on every scroll would be a measurable cost in a wide table. For values cells need to read, close over screen-local state directly when the renderer is inline, or use React context (`useContext`) when the renderer is extracted:\n\n```tsx\nconst FilterContext = React.createContext<string>('')\n\nfunction ProductCell({ row }: CellRenderParams) {\n  const filter = React.useContext(FilterContext)\n  return <span>{highlight(row.data.name, filter)}</span>\n}\n```\n\nThe two channels — React's `useContext` and the table's `context` prop — coexist cleanly: React context for what cell renderers need, the table's `context` prop for what infrastructure needs.\n\n## When to use context vs. closures\n\nFor values defined right above the table in the same component, a closure is simpler:\n\n```tsx\nfunction ProductsTable({ searchQuery, tenantId }: Props) {\n  return (\n    <DataTable\n      EmptyPlaceholder={() => <div>No products match \"{searchQuery}\"</div>}\n      computeRowKey={({ data }) => `${tenantId}:${data.id}`}\n      model={model}\n    >\n      {/* columns */}\n    </DataTable>\n  )\n}\n```\n\n`context` becomes the better choice when the callbacks are extracted (a shared `EmptyPlaceholder` component lives in a design system, not in this file), when one value is consumed by several callbacks at once, or when the table is rendered from a higher-up shell and the callbacks live further down the tree.\n","packages/data-table/docs/7.customization/06.header-slots.md":"---\ntitle: Header Slots\ndescription: Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.\nsidebar:\n  label: Header Slots\n---\n\nA column header has four slot positions for additional UI. Resize handles, reorder grips, sort buttons, filter menus, and selection toggles all mount through one of them.\n\n```text\n┌──────────────── HeaderOverlay ────────────────┐\n│ [HeaderStart]  Header label  [HeaderEnd]  |Edge│\n└───────────────────────────────────────────────┘\n```\n\n- `HeaderStart` — before the label, in normal flex layout. Typical for buttons and menus that sit alongside the label text.\n- `HeaderEnd` — after the label, in normal flex layout. Same use cases as `HeaderStart`, opposite side.\n- `HeaderEdge` — pinned to the column boundary, outside the label's flex flow. Resize handles use this; the position is independent of label length.\n- `HeaderOverlay` — covers the whole header. Drop indicators and custom overlays use this; the slot paints over both the label and any in-flow slots.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge, HeaderEnd } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14 },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [sortField, setSortField] = React.useState<'product' | 'stock'>('product')\n  const sortedRows = React.useMemo(() => {\n    return [...rows].sort((a, b) => String(a[sortField]).localeCompare(String(b[sortField]), undefined, { numeric: true }))\n  }, [sortField])\n\n  React.useEffect(() => {\n    model.setData(sortedRows)\n  }, [sortedRows])\n\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'product')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'stock')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nSlots stack: a single header can host all four at once. Reorder grip on the left, sort button on the right, resize handle at the edge, drop indicator spanning the whole header for the reorder gesture.\n\n```tsx\nimport { HeaderEdge, HeaderEnd, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\n;<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n    <HeaderEdge component={ResizeHandle} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## The render-function rule\n\nWhen a header contains any slots, the label must be a single render-function child (`{() => 'Product'}`) rather than a plain string. The header needs a function to evaluate alongside the slots; a string child only works for plain headers without slots. See [Cell and Header Renderers](/data-table/columns/cell-and-header-renderers/) for the broader story on render functions.\n\n## What slot components receive\n\nSlot components are passed the column key, the column metadata, the column state, and the header element ref — enough to publish engine actions, render indicators, and position overlays without DOM queries. That ref is what lets a drop-indicator overlay measure the header it's painting over, and what lets an edge-pinned handle compute its drag delta.\n\n## Where this shows up in the shadcn registry\n\nThe opt-in column features ship slot components that drop straight into these positions:\n\n- `ResizeHandle` from `@/components/ui/data-table/column-resize` — mounts in `HeaderEdge`. Install with registry item `data-table-resize-handle`. See [Column Resizing](/data-table/columns/column-resizing/).\n- `ReorderGrip` and `ReorderDropZone` from `@/components/ui/data-table/column-reorder` — mount in `HeaderStart` and `HeaderOverlay` respectively. See [Column Reordering](/data-table/columns/column-reordering/).\n- `SortHeaderButton` from `@/components/ui/data-table/column-sort` — mounts in `HeaderEnd` and dispatches a model `sort` action. Install with registry item `data-table-sort-header-button`. See [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\nCustom sort, filter, menu, and selection controls follow the same pattern: a component that takes the slot's render params and returns whatever JSX you need.\n","packages/data-table/docs/7.customization/07.shadcn-wrapper.md":"---\ntitle: Inside the shadcn Wrapper\ndescription: Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.\nsidebar:\n  label: The shadcn Wrapper\n---\n\nThis page applies to the shadcn install path. If you installed `@virtuoso.dev/data-table` directly without the registry, you own the equivalent components in your own files — the wrapper-vs-headless split still applies, but the file locations are yours to decide. See [Headless Install](/data-table/installation/headless/) for that route.\n\nRunning `npx shadcn add` copied a folder of components into `@/components/ui/data-table`. That folder is your code — it ships in your repo, it's checked into your git history, and editing it doesn't fork the table's behavior. The behavior lives somewhere else.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', owner: 'Avery', status: 'Launch ready' },\n  { product: 'USB-C Dock', owner: 'Morgan', status: 'In review' },\n  { product: 'Mechanical Keyboard', owner: 'Riley', status: 'Backorder' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"owner\">\n        <DataTableColumnHeader>Owner</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-muted px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What the wrapper file owns\n\nThe wrapper covers _presentation_:\n\n- default row, header, sticky-column, empty, and loading components\n- Tailwind classes for cells, headers, and group headers\n- the default table frame (`rounded-md border`) and table surface variables\n- ergonomic names (`DataTable`, `DataTableColumn`, `DataTableColumnHeader`, `DataTableCell`)\n- re-exports of the headless hooks and helpers you'll use day-to-day\n\nEdit it like any other component in your app. Tailwind classes, default props, alternate variants, additional re-exports — all fair game. The wrapper file imports behavior from `@virtuoso.dev/data-table`, so your styling edits never fork table mechanics.\n\n## Table surface variables\n\nThe default shadcn wrapper gives `DataTable` an app-level frame (`rounded-md border`) and defines a small set of CSS variables on the same root. The frame border uses `--data-table-border`; callers do not need to add `rounded-md border` at each table instance. Use `className` only for intentional overrides such as `rounded-xl`, `border-0`, `border-2`, `shadow-sm`, or table variable overrides.\n\nThe variables matter most for sticky headers and sticky columns: those pieces are separate overlay containers, so they need an opaque background, but that background should match the table body instead of being hardcoded independently.\n\n| Variable                    | Default                        | Used for                                           |\n| --------------------------- | ------------------------------ | -------------------------------------------------- |\n| `--data-table-bg`           | `hsl(var(--background))`       | Table body, sticky header, sticky column wrappers  |\n| `--data-table-fg`           | `hsl(var(--foreground))`       | Default table/header/group-header text             |\n| `--data-table-border`       | `hsl(var(--border))`           | Row separators, header borders, loading containers |\n| `--data-table-muted`        | `hsl(var(--muted))`            | Group rows, skeleton rows, sticky hover blends     |\n| `--data-table-muted-fg`     | `hsl(var(--muted-foreground))` | Loading and secondary wrapper text                 |\n| `--data-table-row-hover`    | `hsl(var(--muted) / 0.5)`      | Non-sticky row hover background                    |\n| `--data-table-sticky-hover` | a mix of muted and table bg    | Sticky column hover background                     |\n\nIf your app maps shadcn tokens to a different design system, override these variables once on the `DataTable` root or in the copied wrapper defaults. Do not style sticky cells individually just to fix a surface mismatch.\n\n```tsx\n<DataTable\n  className=\"\n    [--data-table-bg:hsl(var(--surface-container-lowest))]\n    [--data-table-fg:hsl(var(--on-surface))]\n    [--data-table-border:hsl(var(--outline))]\n    [--data-table-muted:hsl(var(--surface-container-low))]\n    [--data-table-muted-fg:hsl(var(--on-surface-variant))]\n  \"\n  model={model}\n/>\n```\n\n## What the headless package owns\n\n`@virtuoso.dev/data-table` covers _behavior_:\n\n- virtualization, measurement, scrolling\n- `localModel()` and `remoteModel()`\n- the reactive engine (`engineRef`, `useRemoteCellValue`, `useRemotePublisher`)\n- opt-in feature modules (resize, reorder, visibility, persistence)\n\nBehavior imports always come from `@virtuoso.dev/data-table`. If you find yourself reaching into the headless package to change behavior, that's a signal you want an issue or a PR rather than a fork.\n\n## Feature-specific wrappers\n\nSome opt-in features ship their own wrapper components, following the same presentation/behavior split:\n\n- `data-table-resize-handle` — resize handle UI\n- `data-table-sort-header-button` — model-backed sort button UI\n- `data-table-reorder-grip`, `data-table-reorder-drop-zone`, `data-table-draggable-group-header` — reorder UI\n\nThese names are registry item names, not import paths; shadcn installs them under nested paths such as `@/components/ui/data-table/column-resize` and `@/components/ui/data-table/column-sort`. Each one is header-slot UI you can restyle. The streams they publish to live in the corresponding `@virtuoso.dev/data-table/...` subpaths — see [Header Slots](/data-table/customization/header-slots/) for how those slots compose, and the column feature pages for what each registry component does.\n\n## When you'll come back here\n\nThe wrapper is the right place to change:\n\n- default Tailwind classes on `DataTable`, `DataTableCell`, `DataTableColumnHeader`\n- the default table frame, radius, border, and surface variables\n- the default `Row`, `EmptyPlaceholder`, `LoadingPlaceholder` your team should see\n- ergonomic re-exports you add for your codebase\n\nFor replacing the table's structural components (Row, GroupHeader, sticky wrappers) on a per-instance basis without touching the wrapper file, see [Replacing Internals](/data-table/customization/replacing-internals/).\n","packages/data-table/docs/7.customization/08.card-mode.md":"---\ntitle: Card Mode\ndescription: Switch the same data model between table rows and an automatically measured grid of equal-sized cards.\nsidebar:\n  label: Card Mode\n---\n\nUsers can switch between a table and cards without losing their sorting or filtering. Your application owns the switch; the table keeps the same data model connected in either presentation.\n\n## Switch the presentation\n\nPass `mode=\"card\"` and a `components.Card` component to show cards. The default mode is `\"table\"`. Each switch starts at the first item; it does not restore the previous scroll position or repeat the model handshake.\n\n`Card` receives `{ data, index, context }` inside a wrapper that the library measures. The optional `CardHeader` receives `{ context }` and stays above the cards as a sticky header. Both are inside the same engine provider as the table, so header controls can read model action state and dispatch sorting or filtering actions.\n\nThis example uses the editable [shadcn wrapper](../shadcn-wrapper/) and keeps its column declarations mounted in both modes.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { dispatchModelAction$, localModel, modelActionState$, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport type { CardComponentProps } from '@virtuoso.dev/data-table'\n\ninterface Service {\n  id: number\n  name: string\n  requests: number\n}\n\nconst services: Service[] = Array.from({ length: 1000 }, (_, id) => ({\n  id,\n  name: `Service ${id}`,\n  requests: (id * 137) % 10000,\n}))\n\nfunction ServiceCard({ data }: CardComponentProps<Service>) {\n  return (\n    <article>\n      <h3 className=\"font-medium\">{data.name}</h3>\n      <p>{data.requests.toLocaleString()} requests</p>\n    </article>\n  )\n}\n\nfunction Controls() {\n  const dispatch = usePublisher(dispatchModelAction$)\n  const state = useCellValue(modelActionState$)\n  return (\n    <label className=\"flex items-center gap-2 bg-background p-3\">\n      <input\n        type=\"checkbox\"\n        checked={Boolean(state.sort?.payload)}\n        onChange={(event) => dispatch({ action: 'sort', payload: event.target.checked })}\n      />\n      Descending requests\n    </label>\n  )\n}\n\nconst components = { Card: ServiceCard, CardHeader: Controls }\n\nexport default function App() {\n  const [mode, setMode] = React.useState<'card' | 'table'>('card')\n  const model = React.useMemo(\n    () =>\n      localModel<Service>({\n        data: services,\n        pipeline: ['sort'],\n        actions: {\n          sort: {\n            stage: 'sort',\n            handler: ({ data, payload }: { data: Service[]; payload: unknown }) =>\n              payload ? data.toSorted((a, b) => b.requests - a.requests) : data,\n          },\n        },\n      }),\n    []\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <button className=\"rounded border px-3 py-1\" onClick={() => setMode(mode === 'card' ? 'table' : 'card')}>\n        Show {mode === 'card' ? 'table' : 'cards'}\n      </button>\n      <DataTable<Service, unknown, never>\n        model={model}\n        mode={mode}\n        components={components}\n        computeRowKey={({ data }) => data.id}\n        cardListClassName=\"grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-3\"\n        cardItemClassName=\"h-36 min-w-0 overflow-hidden rounded border p-4\"\n        style={{ height: 380 }}\n      >\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Service</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"requests\">\n          <DataTableColumnHeader>\n            <Controls />\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nFor filtering or remote sorting, connect controls to the same [model actions](../../data-model/) you use in table mode. Card mode does not generate operation controls or choose breakpoints.\n\n## Define a uniform CSS layout\n\nStyle the list with `cardListClassName` and its item wrappers with `cardItemClassName`. The renderer measures one representative wrapper, the container width, and the computed CSS gaps. It derives the number of columns and virtualizes whole lines. There is no numeric card-size prop.\n\n- Every card must have the same outer width and height at the current container width. Matching heights within each individual grid row is not sufficient.\n- Use a regular row-major CSS grid, such as `repeat(auto-fill, minmax(200px, 1fr))`, or a wrapping flex layout with equal-width items and `flex-shrink: 0`.\n- Use `gap`, `row-gap`, and `column-gap` with lengths that resolve to pixels for spacing. Percentage gaps, item margins, spanning items, dense placement, and space-distributed alignment are not supported.\n- Keep list padding and borders off the measured list. The library owns its vertical padding for virtualization. Put decoration on the card wrappers or outside the table.\n- CSS may change dimensions and gaps responsively. A resize observer remeasures the active layout, including after a hidden container becomes visible.\n- Remote placeholders must use the same dimensions as loaded cards. Supply a schema-compatible placeholder and handle it in `Card`.\n\nCard dimensions never enter the table row measurement cache, and row heights never seed card geometry. `itemHeight$` remains a **table-row** measurement API. Each presentation remeasures when activated; retained data and column settings do not imply retained geometry.\n\nUse the local Card Mode Ladle preview to change container width and card height independently, then switch to table rows of a different height.\n\n## Scrolling and loading\n\n`initialLocation`, `scrollToRow$`, and `scrollIntoView$` refer to **model item indexes**, not visual grid-row indexes. The same is true of `viewportRange$` and remote-model viewport requests. `increaseViewportBy` adds pixel overscan, and `onRenderedDataChange` reports the mounted model items.\n\nCards support internal scrolling, `useWindowScroll`, and `customScrollParent`. Their sticky header is included in the visible-height calculation. Empty, initial-loading, refresh-overlay, and append-footer slots use the same model loading state as the table. Switching during a request does not cancel or restart it; the new viewport can request additional data normally.\n\nSee [Scroll Containers](../scroll-containers/) for scroll-owner setup and [Empty and Loading States](../empty-and-loading-states/) for loading and retry controls. The Card Mode Ladle previews include remote offset and append models with delayed requests and simulated errors.\n\n## Limits and recovery\n\nCard mode requires `components.Card` and ungrouped model data. Otherwise it displays a configuration error. Providing the missing component, sending an ungrouped result, or returning to table mode recovers without replacing the model. It does not silently flatten groups or change your grouping action.\n\nColumn headers, column resizing, sticky columns, and column reordering are table-only surfaces. Their declarations and configuration remain available when the application switches back.\n\nUse table mode when users need grouped rows. For unequal card dimensions or masonry placement, use a dedicated layout such as [Virtuoso Masonry](../../../masonry/) instead of this uniform card presentation.\n","packages/data-table/docs/8.examples/01.basic-table.md":"---\ntitle: Basic Table Example\ndescription: Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.\nsidebar:\n  label: Basic Table\n---\n\nThree columns, a hardcoded product list, and the styled shadcn wrapper. The shape you'll arrive at on day one when you drop the table into a screen, before any backend wiring, sorting, or pinning enters the picture.\n\nThe purpose of this example is to fix that day-one shape. The `data` array is passed to `localModel`; each `<DataTableColumn>` declares a `field` and the cell renderer for that field. Nothing is lifted into React state — the table owns its own scroll, selection, and layout state through the engine, and the column declarations live alongside the markup that uses them. Every later example in this section adds one capability on top of this skeleton; if anything below ever feels too dense, this is the page to return to.\n\n## APIs used\n\n- `DataTable` for scrolling and virtualization\n- `DataTableColumn` for declarative columns\n- `DataTableColumnHeader` and `DataTableCell` for presentation\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## What this leaves out\n\n- Grouped rows — add `groups` markers and a `GroupHeaderCell`\n- Sticky pinning — add `sticky=\"left\"` or `sticky=\"right\"`\n- Remote data — swap `localModel` for `remoteModel`\n- Stable row identity — add `computeRowKey` once rows can sort, filter, or move\n","packages/data-table/docs/8.examples/03.grouped-data.md":"---\ntitle: Group Rows by Region and Category\ndescription: Organize a large inventory table into nested groups while preserving virtualization and sticky headers.\nsidebar:\n  label: Grouped Rows\n---\n\nThis example precomputes a flattened grouped result, then lets the table virtualize group headers and product rows together. Regions are top-level group rows, categories are second-level group rows, and products remain ordinary data rows.\n\n## APIs used\n\n- `localModel({ data, groups })` receives the flattened row stream and group markers.\n- `groups: [{ index, level }]` identifies which rows are group headers and how deeply they nest.\n- `GroupHeaderCell` renders the marked group rows instead of rendering column cells for them.\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { groupedProducts } from './data'\nimport type { GroupLabel, Product } from './data'\n\nconst model = localModel(groupedProducts)\n\nfunction rowKey(row: GroupLabel | Product) {\n  return 'id' in row ? row.id : row.groupKey\n}\n\nfunction productName(row: GroupLabel | Product) {\n  return 'name' in row ? row.name : ''\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => rowKey(data)} model={model} style={{ height: 420 }}>\n      <GroupHeaderCell>\n        {({ level, row }) => (\n          <div className={level === 0 ? 'bg-muted px-3 py-2 font-semibold' : 'bg-muted/50 px-6 py-2 font-medium'}>\n            {(row.data as { label: string }).label}\n          </div>\n        )}\n      </GroupHeaderCell>\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => productName(row.data)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"region\">\n        <DataTableColumnHeader>Region</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  region: 'US' | 'EU'\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface GroupLabel {\n  groupKey: string\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\nconst regions: Product['region'][] = ['US', 'EU']\n\nconst products: Product[] = Array.from({ length: 90 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    region: regions[index % regions.length]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport const groupedProducts: {\n  data: (GroupLabel | Product)[]\n  groups: { index: number; level: number }[]\n} = (() => {\n  const data: (GroupLabel | Product)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const region of regions) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ groupKey: `region-${region}`, label: `Region: ${region}` })\n\n    for (const category of categories) {\n      const rows = products.filter((product) => product.region === region && product.category === category)\n      groups.push({ index: data.length, level: 1 })\n      data.push({ groupKey: `region-${region}-category-${category}`, label: `${category} (${rows.length})` })\n      data.push(...rows)\n    }\n  }\n\n  return { data, groups }\n})()\n```\n\n## Notes\n\nGroup labels are virtualized rows, so deep nesting still scrolls cheaply. The practical limit is readability: two levels is usually the ceiling before labels need to be very short. If the grouping changes at runtime, keep stable keys on both product rows and group rows so React can preserve row identity as sections move.\n","packages/data-table/docs/8.examples/04.sticky-columns.md":"---\ntitle: Sticky Columns Example\ndescription: Pin identity and status columns so users can scan wide datasets without losing the row context.\nsidebar:\n  label: Sticky Columns\n---\n\nA product column pinned to the left, a status column pinned to the right, and a dozen metric columns in between to force horizontal scrolling — the only situation where sticky columns matter. The toolbar above the table flips the status column's sticky side at runtime through the engine.\n\n## APIs used\n\n- `sticky=\"left\"` / `sticky=\"right\"` — declarative pin\n- `setColumnSticky$` — runtime sticky changes from external UI\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, setColumnSticky$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport { metricFields, products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'right' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status right\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\" sticky=\"left\">\n          <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n\n        {metricFields.map((field) => (\n          <DataTableColumn field={field} key={field}>\n            <DataTableColumnHeader className=\"min-w-[140px]\">{field}</DataTableColumnHeader>\n            <DataTableCell className=\"tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        ))}\n\n        <DataTableColumn field=\"status\" sticky=\"right\">\n          <DataTableColumnHeader className=\"min-w-[120px]\">Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const metricFields = Array.from({ length: 12 }, (_, index) => `metric${index + 1}`)\n\nexport const products = Array.from({ length: 120 }, (_, rowIndex) => {\n  const stock = (rowIndex * 5) % 32\n  const base: Record<string, string | number> = {\n    id: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n    name: `Product ${rowIndex + 1}`,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n\n  for (const field of metricFields) {\n    base[field] = (rowIndex + 1) * 7 + field.charCodeAt(field.length - 1)\n  }\n\n  return base\n})\n```\n\n## Notes\n\nEach pinned column takes width from the scrollable area. Past two or three sticky columns, horizontal scrolling stops being useful — you've spent your row width on pinned content and the remaining viewport can no longer fit anything meaningful.\n\nThe runtime toggle uses `setColumnSticky$` against the same column key the table assigned during declaration. Look up the key by field with `columns$` once at the call site instead of holding it in component state; the engine is the source of truth.\n","packages/data-table/docs/8.examples/06.column-reorder.md":"---\ntitle: Column Reorder Example\ndescription: Reorder columns with the shadcn drag handle and drop zone components.\nsidebar:\n  label: Column Reorder\n---\n\nEach header carries a `GripVertical` icon at the start and a transparent drop zone across the rest. Drag a grip over another header to move the source column before or after the target.\n\nThe shadcn components import from `@virtuoso.dev/data-table/column-reorder` — consumers that never reorder columns don't ship that reducer. The grip is a Lucide icon, sized and spaced like the other shadcn header affordances so it visually separates from the column title.\n\n## APIs used\n\n- `ReorderGrip` — draggable header affordance\n- `ReorderDropZone` — header drop target\n- `HeaderStart` / `HeaderOverlay` — slot positions for the components\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 400 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nDrag-and-drop is the spreadsheet-style affordance. For accessible alternatives or deterministic test setup, publish `reorderColumns$` from an external menu or button — the streams accept the same operations the drag UI emits.\n","packages/data-table/docs/8.examples/07.local-filter-sort-group.md":"---\ntitle: Local Filter, Sort, Group\ndescription: Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.\nsidebar:\n  label: Local Filter, Sort, Group\n---\n\nA filter dropdown, a sort dropdown, and a grouping dropdown share one `localModel` pipeline. The pipeline runs filter → sort → group; each control publishes one action and only the affected stage (and the stages after it) rerun.\n\n## APIs used\n\n- `localModel()`\n- `pipeline` to declare transformation order\n- `stage` to attach an action to a pipeline step\n- `model.send()` to publish filter, sort, and group changes\n- `GroupHeaderCell`\n\n```tsx live wide file=App.tsx\nimport { useEffect, useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { FilterBar } from './FilterBar'\nimport { groupProducts, products } from './data'\n\nimport type { Product, ProductGroup } from './data'\n\nfunction isProduct(item: Product | ProductGroup): item is Product {\n  return 'id' in item\n}\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n  const [sortBy, setSortBy] = useState<'name' | 'price' | 'revenue'>('name')\n  const [groupBy, setGroupBy] = useState<'none' | 'category' | 'status'>('category')\n\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['filter', 'sort', 'group'],\n      actions: {\n        filter: {\n          stage: 'filter',\n          handler: ({ data, payload }) => {\n            const nextCategory = payload as string\n            const rows = data.filter(isProduct)\n            return nextCategory === 'all' ? rows : rows.filter((product) => product.category === nextCategory)\n          },\n        },\n        sort: {\n          stage: 'sort',\n          handler: ({ data, payload }) => {\n            const field = payload as 'name' | 'price' | 'revenue'\n            return data.filter(isProduct).toSorted((a, b) => (a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0))\n          },\n        },\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as 'none' | 'category' | 'status'),\n        },\n      },\n    })\n  )\n\n  useEffect(() => {\n    model.send({ action: 'filter', payload: category })\n  }, [category, model])\n\n  useEffect(() => {\n    model.send({ action: 'sort', payload: sortBy })\n  }, [sortBy, model])\n\n  useEffect(() => {\n    model.send({ action: 'group', payload: groupBy })\n  }, [groupBy, model])\n\n  return (\n    <div className=\"space-y-4\">\n      <FilterBar\n        category={category}\n        groupBy={groupBy}\n        onCategoryChange={setCategory}\n        onGroupByChange={setGroupBy}\n        onSortByChange={setSortBy}\n        sortBy={sortBy}\n      />\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <GroupHeaderCell className=\"bg-muted px-3 py-2 font-medium\">{({ row }) => row.data.label}</GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=FilterBar.tsx\ninterface FilterBarProps {\n  category: string\n  groupBy: 'none' | 'category' | 'status'\n  onCategoryChange: (value: string) => void\n  onGroupByChange: (value: 'none' | 'category' | 'status') => void\n  onSortByChange: (value: 'name' | 'price' | 'revenue') => void\n  sortBy: 'name' | 'price' | 'revenue'\n}\n\nexport function FilterBar({ category, groupBy, onCategoryChange, onGroupByChange, onSortByChange, sortBy }: FilterBarProps) {\n  return (\n    <div className=\"flex flex-wrap gap-3\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onCategoryChange(event.target.value)}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onSortByChange(event.target.value as 'name' | 'price' | 'revenue')}\n        value={sortBy}\n      >\n        <option value=\"name\">Sort by name</option>\n        <option value=\"price\">Sort by price</option>\n        <option value=\"revenue\">Sort by revenue</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onGroupByChange(event.target.value as 'none' | 'category' | 'status')}\n        value={groupBy}\n      >\n        <option value=\"none\">No groups</option>\n        <option value=\"category\">Group by category</option>\n        <option value=\"status\">Group by status</option>\n      </select>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n  revenue: number\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface ProductGroup {\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nexport const products: Product[] = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    price: 49 + (index % 11) * 13,\n    revenue: 1_100 + index * 85,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport function groupProducts(data: Product[], groupBy: 'none' | 'category' | 'status') {\n  if (groupBy === 'none') {\n    return data\n  }\n\n  const groups = new Map<string, Product[]>()\n\n  for (const item of data) {\n    const key = groupBy === 'category' ? item.category : item.status\n    const bucket = groups.get(key)\n    if (bucket) {\n      bucket.push(item)\n    } else {\n      groups.set(key, [item])\n    }\n  }\n\n  const flatData: (Product | ProductGroup)[] = []\n  const markers: { index: number; level: number }[] = []\n\n  for (const [key, items] of groups) {\n    markers.push({ index: flatData.length, level: 0 })\n    flatData.push({ label: `${key} (${items.length})` })\n    flatData.push(...items)\n  }\n\n  return { data: flatData, groups: markers }\n}\n```\n\n## When to outgrow this shape\n\n- The full dataset doesn't fit in memory — switch to `remoteModel` and move filter/sort/group into request params.\n- Filtering rules need server-side evaluation (permissions, complex SQL, full-text search) — keep the table local for display but fetch filtered rows.\n","packages/data-table/docs/8.examples/08.remote-offset.md":"---\ntitle: Server-side Paging\ndescription: Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.\nsidebar:\n  label: Paging\n---\n\nOffset mode requests only the pages near the viewport. Placeholders fill the rest of the scroll height while pages are pending, so total scroll size stays correct from the first render.\n\nThe fetch function receives `{ offset, limit, params, signal }` and returns `{ rows, totalCount }`.\n\n## APIs used\n\n- `remoteModel()`\n- `defaultOffsetViewportHandler` — built-in handler for offset/limit backends\n- `AbortSignal`-aware fetch functions\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          handler: ({ params, payload }) => ({ ...params, category: payload as string }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => {\n          const nextCategory = event.target.value\n          setCategory(nextCategory)\n          model.send({ action: 'filter', payload: nextCategory })\n        }}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(220, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nOffset mode needs the backend to answer count-aware slice queries efficiently. For cursor APIs, search-after pagination, or feeds that don't expose a total count, see the [Cursor Load More example](/data-table/examples/remote-append/).\n","packages/data-table/docs/8.examples/09.remote-append.md":"---\ntitle: Endless Scrolling\ndescription: Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.\nsidebar:\n  label: Endless Scrolling\n---\n\nEndless scrolling — append mode — accumulates rows as the user reaches the end of the loaded list. New pages are concatenated onto what's already loaded; there is no total count and no jump-to-row. The fetch function receives `{ cursor, limit, params, signal }` and returns `{ rows, cursor, hasMore }`.\n\n## APIs used\n\n- `mode: 'append'` — turns on append-mode bookkeeping\n- `defaultAppendViewportHandler` — built-in viewport handler that triggers `loadMore` near the end\n- `model.send({ action: 'loadMore' })` — manual load-more trigger\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultAppendViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts } from './api'\n\nimport type { Product } from './api'\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product>({\n      fetch: fetchProducts,\n      initialParams: {},\n      mode: 'append',\n      onViewportChange: defaultAppendViewportHandler,\n      pageSize: 20,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <Button onClick={() => model.send({ action: 'loadMore' })} variant=\"outline\">\n        Load more\n      </Button>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { AppendFetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 180 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n}))\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: AppendFetchParams<Record<string, never>>) {\n  await pause(220, params.signal)\n  const start = (params.cursor as number | undefined) ?? 0\n  const rows = allProducts.slice(start, start + params.limit)\n  const nextCursor = start + rows.length\n\n  return {\n    cursor: nextCursor,\n    hasMore: nextCursor < allProducts.length,\n    rows,\n  }\n}\n```\n\n## When this doesn't fit\n\nUsers can't jump to row 50,000 without loading everything before it. For known total counts and arbitrary positioning, see the [Server-side Pages example](/data-table/examples/remote-offset/).\n","packages/data-table/docs/8.examples/10.table-instance-dashboard.md":"---\ntitle: External Toolbar\ndescription: Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.\nsidebar:\n  label: External Toolbar\n---\n\nA control panel beside the table reads `scrollLocation$` and publishes `scrollToRow$` and `setColumnSticky$` through a shared `engineRef`. Virtualization and feature state stay inside the table engine; React state stays out of it.\n\n## APIs used\n\n- `useEngineRef()`\n- `useRemoteCellValue()`\n- `useRemotePublisher()`\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, useEngineRef } from '@virtuoso.dev/data-table'\n\nimport { ControlPanel } from './ControlPanel'\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n\n  return (\n    <div className=\"grid gap-4 lg:grid-cols-[280px_minmax(0,1fr)]\">\n      <ControlPanel engineRef={engineRef} />\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=ControlPanel.tsx\nimport { Button } from '@/components/ui/button'\nimport { columns$, scrollLocation$, scrollToRow$, setColumnSticky$, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport type { ColumnInfo, EngineRef } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nexport function ControlPanel({ engineRef }: { engineRef: EngineRef }) {\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4 rounded-xl border bg-muted/30 p-4 text-sm\">\n      <div className=\"space-y-1\">\n        <p className=\"font-semibold\">Table controls</p>\n        <p className=\"text-muted-foreground\">\n          {scrollLocation === undefined ? 'Waiting for table state…' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n        </p>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 79, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 80\n        </Button>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nThe cells and actions you publish to (`scrollToRow$`, `setColumnSticky$`, `scrollLocation$`, etc.) are the public API surface for this table — pin their names in tests and treat renames the same way you'd treat a prop rename.\n","packages/data-table/docs/8.examples/11.scrollbar-overlay.md":"---\ntitle: Keep Sticky Columns Clear of Overlay Scrollbars\ndescription: Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.\nsidebar:\n  label: Overlay Scrollbars\n---\n\nmacOS overlay scrollbars sit on top of content and can cover the last sticky-right column. The header and cell render params expose `overlaidByScrollbar` — true when an overlay scrollbar covers this cell — so you can add compensating right padding only when needed.\n\n## APIs used\n\n- `overlaidByScrollbar` — render-param flag in cell and header renderers\n- `sticky=\"right\"` — column pin where the bug shows up\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { columns, rows } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 400 }}>\n      <DataTableColumn field=\"sku\" sticky=\"left\">\n        <DataTableColumnHeader>SKU</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      {columns.map((column) => (\n        <DataTableColumn field={column} key={column}>\n          <DataTableColumnHeader className=\"min-w-[130px]\">{column}</DataTableColumnHeader>\n          <DataTableCell>{({ row }) => String(row.data[column])}</DataTableCell>\n        </DataTableColumn>\n      ))}\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[150px] justify-end\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Actions\n            </span>\n          )}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right text-sm text-muted-foreground\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Edit · Duplicate\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const columns = Array.from({ length: 16 }, (_, index) => `metric${index + 1}`)\n\nexport const rows = Array.from({ length: 120 }, (_, rowIndex) => {\n  const row: Record<string, string> = {\n    sku: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n  }\n\n  for (const column of columns) {\n    row[column] = `R${rowIndex + 1}-${column}`\n  }\n\n  return row\n})\n```\n\n## Notes\n\nOnly sticky-right columns need this. The bug is platform-specific (macOS, sometimes iPadOS) — easy to miss in development on other platforms, so bake the padding in early.\n\nColumn widths come from header measurements. Set `min-w-*` on the header only; never on `DataTableCell`. A width class on the cell competes with the engine's column-width sync and the cells start overlapping each other when the table re-measures.\n","packages/data-table/docs/8.examples/12.custom-scroll-container.md":"---\ntitle: Embed a Table in a Shared Scroll Area\ndescription: Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.\nsidebar:\n  label: Shared Scroll Area\n---\n\nThe table renders inside a parent with its own `overflow: auto`, so notes, filters, and the table body all scroll together. `customScrollParent` tells the table to drive virtualization from that outer element instead of its own scroller.\n\n## APIs used\n\n- `customScrollParent` — attaches the table to an external scrolling element\n- An outer container with `overflow: auto` and a real height\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null)\n\n  return (\n    <div className=\"rounded-xl\">\n      <div className=\"border-b px-4 py-3\">\n        <p className=\"font-semibold\">Shared scroll surface</p>\n        <p className=\"text-sm text-muted-foreground\">The note above the table scrolls together with the rows.</p>\n      </div>\n\n      <div className=\"h-[420px] overflow-auto\" ref={setScrollParent}>\n        <div className=\"border-b px-4 py-3 text-sm text-muted-foreground\">\n          Filters, charts, or help text can live in the same scrolling context as the table body.\n        </div>\n\n        <DataTable className=\"min-w-[720px]\" customScrollParent={scrollParent} model={model}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Product</DataTableColumnHeader>\n            <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"category\">\n            <DataTableColumnHeader>Category</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"region\">\n            <DataTableColumnHeader>Region</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"status\">\n            <DataTableColumnHeader>Status</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 160 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n```\n\n## Notes\n\nThe outer container's height bounds the entire layout, including the table. Sticky table headers now interact with sibling sticky elements inside the same scroll context — test both together.\n\nDo not set `style={{ height }}` on the table when `customScrollParent` is in use. The table grows to its virtualized content height; the outer container's scroll height drives virtualization. A fixed table height clips the rows before the scroll parent can reach them, and the bottom rows appear to never load.\n","packages/data-table/docs/8.examples/13.column-resizing.md":"---\ntitle: Column Resizing Example\ndescription: Add resize handles and external width controls to a table.\nsidebar:\n  label: Column Resizing\n---\n\nA resize handle on every column plus an external button that sets the product column to a fixed width. Both publish to the same `resizeColumn$` stream; `columnWidths$` exposes the realized layout for the readout.\n\n## APIs used\n\n- `HeaderEdge` — slot position for the handle\n- `ResizeHandle` — shadcn drag affordance\n- `resizeColumn$` — width override stream\n- `columnWidths$` — realized layout state for display\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel, columnWidths$, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnWidthOverrides$, resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nimport { products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const widths = useRemoteCellValue(columnWidths$, engineRef)\n  const resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n  const resetColumnWidthOverrides = useRemotePublisher(resetColumnWidthOverrides$, engineRef)\n\n  const nameKey = findColumnKey(columns, 'name')\n  const nameWidth = nameKey && widths ? Math.round(widths.get(nameKey) ?? 0) : 0\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n        <Button\n          disabled={!nameKey}\n          onClick={() => {\n            if (nameKey) {\n              resizeColumn({ key: nameKey, width: 280 })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Set product to 280px\n        </Button>\n        <Button onClick={() => resetColumnWidthOverrides()} variant=\"outline\">\n          Reset widths\n        </Button>\n        <span className=\"text-muted-foreground\">Product width: {nameWidth || 'measuring'}px</span>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Stock'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n```\n\n## Notes\n\nDrag handles cover spreadsheet-style interaction. External controls cover presets, reset buttons, and commands that shouldn't depend on a pointer — both publish to the same stream, so they stay consistent.\n","packages/data-table/docs/8.examples/14.column-visibility.md":"---\ntitle: Column Visibility Example\ndescription: Build a toolbar that hides and restores columns at runtime.\nsidebar:\n  label: Column Visibility\n---\n\nA toolbar reads `columns$` and `columnVisibilityState$` through an `engineRef` and publishes `setColumnVisibility$` and `resetColumnVisibility$`. The `internalId` column starts hidden via `visible={false}` and can be toggled back on from the toolbar.\n\n## APIs used\n\n- `columns$` — column declarations from the engine\n- `columnVisibilityState$` — current visibility overrides\n- `setColumnVisibility$` / `resetColumnVisibility$` — toggle and reset\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n          return (\n            <Button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} variant={visible ? 'secondary' : 'outline'}>\n              {visible ? 'Hide' : 'Show'} {column.field}\n            </Button>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    internalId: `inv_${1000 + index}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nPersisted visibility is keyed by `field`, not by the generated runtime column key. Renaming a field invalidates its saved visibility — see [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the migration rule.\n","packages/data-table/docs/8.examples/15.state-persistence.md":"---\ntitle: State Persistence Example\ndescription: Persist a remote filter action and column widths for a remote table.\nsidebar:\n  label: State Persistence\n---\n\nA remote category filter and the column widths both survive a reload. The model action opts into persistence with `persistence: true`; the column-width adapter saves resize overrides by field name. Both restore on mount through `DataTableStatePersistence`.\n\n## APIs used\n\n- `DataTableStatePersistence` — mounts the adapters under one storage key\n- `modelStatePersistenceAdapter()` — persists model action state\n- `columnWidthPersistenceAdapter()` — persists column width overrides\n- `persistence: true` — opt-in flag on a model action\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst adapters = [modelStatePersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          persistence: true,\n          handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  function applyCategory(nextCategory: string) {\n    model.send({ action: 'filter', payload: nextCategory })\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {['all', 'Office', 'Peripherals', 'Audio'].map((value) => (\n          <Button key={value} onClick={() => applyCategory(value)} variant=\"outline\">\n            {value}\n          </Button>\n        ))}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-example\" />\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Price'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## Notes\n\nRestore happens after columns and the model register with the engine, so a brief unpersisted render is normal on mount. Keep the `adapters` array stable (define outside the component or wrap in `useMemo`), and bump `storageKey` or `resetKey` when saved state should no longer apply.\n","packages/data-table/docs/8.examples/16.remote-column-sorting.md":"---\ntitle: Remote Column Sorting\ndescription: Use shadcn header buttons to send sort params to a remote data model.\nsidebar:\n  label: Remote Sorting\n---\n\nEach sortable column header sends a `sort` action to the remote model. The action updates the request params, the model refetches from the first page, and the active header button reads the current sort from `modelActionState$`. Do not keep a second `sortBy` / `sortOrder` React state just to paint the header; seed the model with `initialActions` for a default sort and let `modelActionState$` be the source of truth.\n\nThe shadcn `SortHeaderButton` defaults to a payload shaped as `{ field, direction }`, where `direction` cycles through `'asc'`, `'desc'`, and no sort.\n\nInstall the optional sort button wrapper before importing it:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-sort`.\n\n## APIs used\n\n- `remoteModel()` — refetches rows when request params change\n- `SortHeaderButton` — shadcn slot component for column-header sorting\n- `HeaderEnd` — places the sort button after the header label\n- `computeRowKey` — keeps row identity stable when sorting reorders rows\n- `initialActions` — seeds the active sort in model action state\n- `modelStatePersistenceAdapter()` — optionally persists the sort action payload across reloads\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEnd } from '@/components/ui/data-table'\nimport { SortHeaderButton } from '@/components/ui/data-table/column-sort'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst defaultSort = { field: 'name', direction: 'asc' } satisfies NonNullable<Query['sort']>\nconst persistenceAdapters = [modelStatePersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        sort: {\n          persistence: true,\n          strategy: 'supersede',\n          handler: ({ params, payload }) => ({ ...params, sort: payload as Query['sort'] }),\n        },\n      },\n      fetch: fetchProducts,\n      initialActions: [{ action: 'sort', payload: defaultSort }],\n      initialParams: { sort: defaultSort },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 420 }}>\n      <DataTableStatePersistence adapters={persistenceAdapters} storageKey=\"remote-column-sorting-example\" />\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Price'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => `$${cellValue}`}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  sort?: {\n    field: 'name' | 'category' | 'price'\n    direction: 'asc' | 'desc'\n  }\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nfunction compareProducts(sort: NonNullable<Query['sort']>) {\n  return (left: Product, right: Product) => {\n    const leftValue = left[sort.field]\n    const rightValue = right[sort.field]\n    const result =\n      typeof leftValue === 'number' && typeof rightValue === 'number'\n        ? leftValue - rightValue\n        : String(leftValue).localeCompare(String(rightValue), undefined, { numeric: true })\n\n    return sort.direction === 'asc' ? result : -result\n  }\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.sort ? allProducts.toSorted(compareProducts(params.params.sort)) : allProducts\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nIf your API uses a different query shape, render the button manually in `HeaderEnd` and pass `action`, `field`, `getDirection`, or `getPayload` props to `SortHeaderButton`. Copy the installed component into your app-specific table wrapper when every table in the app should share the same sort payload shape.\n","packages/data-table/docs/9.guides/01.testing.md":"---\ntitle: Testing\ndescription: Make table behavior deterministic in tests by providing explicit test dimensions.\nsidebar:\n  label: Testing\n---\n\n`VirtuosoDataTableTestingContext` hands the table fixed `itemHeight` and `viewportHeight` values so virtualization decisions don't depend on real layout. Use it in component tests that assert visible rows, loaded ranges, or loading/empty state.\n\nIn browser tests (Playwright, real layout), prefer the actual measurements — only reach for the testing context when layout is missing or timing-dependent.\n\n## Fixed-size test table\n\n```tsx live\nimport { localModel, VirtuosoDataTableTestingContext } from '@virtuoso.dev/data-table'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nconst rows = Array.from({ length: 20 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 240 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n`itemHeight` is the row height the table assumes; `viewportHeight` is the visible vertical space. In this example, 240px ÷ 40px = six rows before overscan.\n\nThe table still needs a real height in its own layout. Match the provider dimensions and the table height unless the test specifically targets a mismatch.\n\n## What to assert\n\nAssert behavior, not exact DOM node counts — overscan, sticky headers, and measurement buffers can render extra rows.\n\nGood browser-test assertions:\n\n- a row near the top is visible before scrolling\n- a target row becomes visible after `scrollToRow$` or user scrolling\n- an empty placeholder is visible when the model has no rows\n- a remote loading overlay appears while a request is pending\n\nFor component tests, wrap the table once in a shared helper:\n\n```tsx\nfunction renderTestTable(children: React.ReactNode) {\n  return render(\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      {children}\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n## Browser vs. component tests\n\n- Sticky columns, resize handles, drag interactions, custom scroll parents — test in the browser with real layout.\n- Visible-row and loading-state assertions — use `VirtuosoDataTableTestingContext` in component tests.\n- Keep data generation simple. Behavior is what you're validating, not visual polish.\n","packages/data-table/docs/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F02.performance.md -->","packages/data-table/docs/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F03.troubleshooting.md -->","packages/data-table/docs/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","packages/data-table/docs/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F05.debug-instrumentation.md -->","packages/data-table/src/features/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fsrc%2Ffeatures%2FREADME.md -->","packages/gurx/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fgurx%2FREADME.md -->","packages/masonry/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmasonry%2FREADME.md -->","packages/message-list/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2FREADME.md -->","packages/message-list/docs/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F10.smooth-scrolling.md -->","packages/message-list/docs/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F11.testing.md -->","packages/message-list/docs/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F15.imperative-data-api.md -->","packages/message-list/docs/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F19.resize-observer-errors.md -->","packages/message-list/docs/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F01.intro.md -->","packages/message-list/docs/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F02.message-list.md -->","packages/message-list/docs/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F03.loading-older-messages.md -->","packages/message-list/docs/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","packages/message-list/docs/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F05.receive-messages.md -->","packages/message-list/docs/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F06.send-messages.md -->","packages/message-list/docs/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F07.multiple-channels.md -->","packages/message-list/docs/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F20.scroll-modifier.md -->","packages/message-list/docs/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F21.controlled-mode-recipes.md -->","packages/message-list/docs/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F01.messaging.md -->","packages/message-list/docs/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F02.ai-chatbot.md -->","packages/message-list/docs/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F03.gemini.md -->","packages/message-list/docs/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F04.reactions.md -->","packages/message-list/docs/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F05.date-separators.md -->","packages/message-list/docs/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F06.scroll-to-reply.md -->","packages/message-list/docs/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F07.grouped-messages.md -->","packages/message-list/docs/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F4.headers-footers.md -->","packages/message-list/docs/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F5.context.md -->","packages/message-list/docs/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F6.item-keys.md -->","packages/message-list/docs/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F7.hooks.md -->","packages/message-list/docs/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F8.custom-scrollbar.md -->","packages/message-list/docs/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F80.licensing.md -->","packages/message-list/docs/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F9.scrolling-to-item.md -->","packages/react-virtuoso/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2FREADME.md -->","packages/react-virtuoso/docs/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fauto-resizing.md -->","packages/react-virtuoso/docs/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fbasic-usage.md -->","packages/react-virtuoso/docs/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustom-scroll-container.md -->","packages/react-virtuoso/docs/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustomize-rendering.md -->","packages/react-virtuoso/docs/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fendless-scrolling.md -->","packages/react-virtuoso/docs/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ffooter.md -->","packages/react-virtuoso/docs/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fhorizontal-mode.md -->","packages/react-virtuoso/docs/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Finitial-index.md -->","packages/react-virtuoso/docs/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fkeyboard-navigation.md -->","packages/react-virtuoso/docs/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fpress-to-load-more.md -->","packages/react-virtuoso/docs/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Frange-change-callback.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-handling.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-to-index.md -->","packages/react-virtuoso/docs/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftailwind-test.md -->","packages/react-virtuoso/docs/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftop-items.md -->","packages/react-virtuoso/docs/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fwindow-scrolling.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","packages/react-virtuoso/docs/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","packages/react-virtuoso/docs/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Fbasic-table.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-grouped.md -->","packages/react-virtuoso/docs/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","packages/react-virtuoso/docs/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmocking-in-tests.md -->","packages/react-virtuoso/docs/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","packages/react-virtuoso/docs/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Ftanstack-table-integration.md -->","packages/react-virtuoso/docs/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F6.troubleshooting.md -->","packages/reactive-engine-core/.claude/settings.local.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2F.claude%2Fsettings.local.json -->","packages/reactive-engine-core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2FREADME.md -->","packages/reactive-engine-core/docs/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F01.core-concepts.md -->","packages/reactive-engine-core/docs/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F02.engine-and-lifecycle.md -->","packages/reactive-engine-core/docs/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F03.transactions.md -->","packages/reactive-engine-core/docs/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F04.operators-and-combinators.md -->","packages/reactive-engine-core/docs/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F05.building-on-the-engine.md -->","packages/reactive-engine-core/docs/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F06.diagnostics.md -->","packages/reactive-engine-query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2FREADME.md -->","packages/reactive-engine-query/docs/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2Fdocs%2F01.queries-and-mutations.md -->","packages/reactive-engine-react/.claude/settings.local.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2F.claude%2Fsettings.local.json -->","packages/reactive-engine-react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2FREADME.md -->","packages/reactive-engine-react/docs/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2Fdocs%2F01.react-integration.md -->","packages/reactive-engine-router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2FREADME.md -->","packages/reactive-engine-router/docs/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2Fdocs%2F01.routing.md -->","packages/reactive-engine-storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2FREADME.md -->","packages/reactive-engine-storage/docs/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2Fdocs%2F01.storage-links.md -->","packages/virtuoso-skills/.claude-plugin/plugin.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2F.claude-plugin%2Fplugin.json -->","packages/virtuoso-skills/CHANGELOG.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FCHANGELOG.md -->","packages/virtuoso-skills/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FREADME.md -->","packages/virtuoso-skills/package.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fpackage.json -->","packages/virtuoso-skills/skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","packages/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","packages/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","packages/virtuoso-skills/skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","packages/virtuoso-skills/skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","packages/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","packages/virtuoso-skills/skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","packages/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","packages/virtuoso-skills/skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","packages/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","packages/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","packages/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","packages/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","packages/virtuoso-skills/skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","packages/virtuoso-skills/skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","packages/virtuoso-skills/skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","packages/virtuoso-skills/skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","packages/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","packages/virtuoso-skills/skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","packages/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","packages/virtuoso-skills/skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","packages/virtuoso-skills/skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","packages/virtuoso-skills/skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","packages/virtuoso-skills/skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","packages/virtuoso-skills/skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","packages/virtuoso-skills/tsconfig.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Ftsconfig.json -->","plugins/virtuoso-skills/.codex-plugin/plugin.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2F.codex-plugin%2Fplugin.json -->","plugins/virtuoso-skills/skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","plugins/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","plugins/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","plugins/virtuoso-skills/skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","plugins/virtuoso-skills/skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","plugins/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","plugins/virtuoso-skills/skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","plugins/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","plugins/virtuoso-skills/skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","plugins/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","plugins/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","plugins/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","plugins/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","plugins/virtuoso-skills/skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","plugins/virtuoso-skills/skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","plugins/virtuoso-skills/skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","plugins/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","plugins/virtuoso-skills/skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","plugins/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","plugins/virtuoso-skills/skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2FSKILL.md -->","skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2FREADME.md -->","skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2FSKILL.md -->","skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F11.testing.md -->","skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F5.context.md -->","skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F7.hooks.md -->","skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F80.licensing.md -->","skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2FREADME.md -->","skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2FSKILL.md -->","skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2FREADME.md -->","skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2FSKILL.md -->","skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->"},"files":{".agents/plugins/marketplace.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.agents%2Fplugins%2Fmarketplace.json -->",".claude-plugin/marketplace.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.claude-plugin%2Fmarketplace.json -->",".codex/skills/docs-guidance/SKILL.md":"---\nname: docs-guidance\ndescription: Revises project documentation for human readability, conceptual flow, and example safety. Covers concept pages, example pages, troubleshooting, installation, and migration docs. Use when writing or reviewing docs, when the user says a page \"feels off\", \"feels weird\", \"feels sudden\", or \"needs attention\", when asked to make a doc clearer or to rewrite a section, when fixing cold or abrupt openings, when a section opens with the API mechanism instead of the user's problem, or when comparing prose against a readability standard.\n---\n\n# Docs Guidance\n\nUse this skill for project documentation work. Treat docs quality as a product concern: the reader should understand where they are, why the topic exists, what shape the relevant data or API has, and when the shown pattern stops fitting.\n\nThis skill is intentionally general. Apply it to package docs, guides, examples, troubleshooting pages, migration notes, and installation pages across the repo.\n\n## Workflow\n\n1. Start from the reader's task, decision, or confusion, not from the API list.\n2. Identify the core contract the page must teach: state ownership, data shape, lifecycle, render boundary, persistence keying, configuration shape, backend shape, or control surface.\n3. Rewrite around the path: user problem → concept → contract → runnable example → limits and next step.\n4. Keep examples safe for real usage, not just short.\n5. End with a concrete escape hatch: where to go when this example no longer fits.\n\n## Core Readability Rules\n\nThese are the non-negotiables. Most doc-quality problems trace to violating one of them:\n\n- **Start from the reader's situation.** Open each page with the user problem, decision, or concrete setup. Never begin with an unexplained abstraction or API name unless the page is explicit API reference.\n- **Describe, don't dramatize.** State what the thing is and what it does in literal terms. Don't open with what users feel or expect (\"users expect…\", \"users develop opinions about…\"); don't give code or UI intentions, conflicts, or effort (\"the slot fights for space\", \"the header expects to do work\"); don't enumerate capabilities as atmospheric color when the page only covers some of them. Narrative motivation, anthropomorphism, and vibes lists all signal the writer didn't commit to a direct statement of the mechanism. See [diagnostics.md → Framing tone](references/diagnostics.md#framing-tone) for the specific smells.\n- **Define concepts before using them.** Don't introduce project terms (`engine`, `slot`, `pipeline`, `marker`, `adapter`, `state cell`, `registry`, `provider`) without grounding them in the user's mental model first.\n- **Explain why before how.** Show the pressure that creates the API — rows need stable identity, requests can be superseded, persisted state needs a stable key — before showing the call signature.\n- **For feature pages, lead with user-facing behavior.** If a feature has an end-user surface (something the user sees, clicks, drags, toggles), demonstrate that surface — visibly, interactively when it's interactive — before the developer-facing config or data contract. The developer is reading the page to enable an end-user behavior; the end-user behavior is the topic, not the config that supports it.\n- **Show the contract just before the recipe it grounds.** When you're about to drop a code block whose behavior depends on a contract, render the data/state/config shape one paragraph before the code so the example doesn't have to be reverse-engineered. This is a within-section pattern, not a whole-page ordering rule — at page level, the user-facing behavior (for feature pages) or the reader's problem (for concept pages) comes first.\n- **End with a concrete next step.** Every section should exit with a pointer to the next decision, deeper example, or escape hatch.\n\n## When to consult deeper material\n\nLoad these references when the work calls for them. Keeping them out of this file is deliberate — load only what the current task needs.\n\n- **[references/diagnostics.md](references/diagnostics.md)** — the invariants framework (what every page must teach) and the failure smells (what to look for when reviewing). Consult when reviewing, diagnosing, or auditing existing prose.\n- **[references/page-shapes.md](references/page-shapes.md)** — templates for concept, example, troubleshooting, installation, migration, and limitation-focused pages, plus templates for section-level shapes (gotcha-led, comparison, topic-first-alternative-second). Consult when writing a new page or restructuring an existing one.\n- **[references/example-craft.md](references/example-craft.md)** — rules for the code examples themselves: stable instances, variable-shape demo data, identifier conflict resolution, defensive-to-positive framing, and styling-layer choices. Consult when writing or auditing example code blocks.\n\n## Final Check\n\nBefore finishing, confirm:\n\n- Can a reader understand the first paragraph without reading another page?\n- Are all public terms introduced before use?\n- Does every code example model safe state ownership and lifecycle?\n- Does every advanced mechanism explain the user pressure that creates it?\n- Does the page tell the reader what to do next when this pattern is not enough?\n",".codex/skills/docs-guidance/agents/openai.yaml":"interface:\n  display_name: 'Docs Guidance'\n  short_description: 'Write readable project docs'\n  default_prompt: 'Use $docs-guidance to revise this documentation for human readability, conceptual flow, and safe examples.'\n",".codex/skills/docs-guidance/references/diagnostics.md":"# Diagnostics: Invariants and Smells\n\nTwo complementary frameworks. Invariants describe what every page must teach. Smells describe what to look for when something feels off.\n\nConsult this file when reviewing existing prose, auditing a page against a standard, or deciding which rule to apply to a vague \"this feels off\" comment.\n\n## Documentation Invariants\n\nWhen revising a page, look for the invariant that matters most and make it explicit. Do not let a page merely say an API exists — it should explain the invariant the API protects.\n\n- **Ownership.** Where state, configuration, data, or styling should live.\n- **Identity.** Which keys, names, IDs, fields, or handles must remain stable.\n- **Shape.** The exact shape of data, config, events, return values, or rendered structure.\n- **Lifecycle.** What is created once, reused, reset, persisted, fetched, cancelled, or cleaned up.\n- **Boundary.** Which package, wrapper, component, hook, adapter, or integration owns each responsibility.\n- **Failure mode.** What breaks when the pattern is used in the wrong context.\n- **Escape hatch.** Which API, guide, or example to use when the simple pattern no longer fits.\n\nA page that names its API but does not teach one of these invariants is incomplete. The invariant is the load-bearing knowledge; the API is just the surface for invoking it.\n\n## Readability Failure Smells\n\nRevise when a page has any of these. Grouped by where the smell shows up.\n\n### Opening and framing\n\n- Starts with a library abstraction before explaining the user problem.\n- Begins a section as if the reader already accepted an unstated premise.\n- Opens a section by recommending the alternative the section is meant to compare against. The section is supposed to teach _this_ approach — defer the alternative to a sidebar at the end.\n- Limitation-focused section opens with the recipe instead of the bug the recipe prevents. Lead with the trap, then the fix.\n\n### Concepts and terminology\n\n- Uses a term that only makes sense after reading another page.\n- Drifts into odd phrases, private shorthand, or terms the reader would not naturally use.\n- The same identifier appears in the same doc with different shapes (e.g., two helpers named `fieldsFromRows` returning different types). Rename one to disambiguate.\n\n### Section purpose\n\n- Describes mechanics but not the reason the mechanic exists.\n- Says what is possible but not when to choose it.\n- Explains internal architecture when the user needs a decision rule.\n- The section's audience overlaps poorly with the parent page's audience — it likely belongs in a different page.\n- Has paragraphs that restate the previous paragraph's claim (\"This means…\", \"This keeps things X…\", \"In other words…\"). Meta-commentary that adds no new information; drop it.\n\n### Structure and navigation\n\n- Headings accurate but not helpful to a scanning reader. A heading should announce what the reader will learn, not just name the API.\n- Jumps from install or setup straight into advanced options without a \"what now\" bridge.\n- Cross-link text is vague (\"see X\"). The link text should answer \"why follow this.\" Prefer `[Generated columns](…) for type-aware columns` over `see [Generated columns](…)`.\n\n### Example quality\n\n- Presents \"correct\" code that would be unsafe in real usage.\n- Demonstrates a feature for dynamic or variable data using uniform example rows — silently hiding the variability question the feature exists to answer.\n\n### Feature pages\n\n- Page teaches the data contract or config object thoroughly but never shows what the end user can do with the feature. The reader closes the page knowing the shape but unable to picture the result or the interaction.\n- An interactive feature (the user clicks, drags, toggles, regroups, reorders) is demonstrated only as a static example. No control to click, no model action paired with UI. If the feature is interactive, the example must show the interaction.\n- Page leads with the data shape rather than what the end user sees. Almost always a sign that the writer let the cleanest-to-explain thing become the lead. The fix is to invert: end-user behavior first, contract last.\n- Interactive variations (Group By Category, Sort by Name) appear only as a bullet in a list of options, never as a worked example with the UI surface that fires them.\n\n### Framing tone\n\n- Uses defensive framing for things the library deliberately leaves to the user. (\"The package does not ship X\" → \"X lives in your code so the rules stay obvious next to the table.\")\n- Apologetic phrasing for intentional design choices (\"currently does not re-fetch\") instead of confident phrasing (\"intentionally one-shot\").\n- **Narrative motivation in openers.** Opens with what users want, feel, or expect instead of what the page documents. _\"Users expect headers to do work\"_, _\"A column header is rarely just a label\"_, _\"Wide tables push key context off-screen the moment a reader scrolls\"_ — these are scene-setting. The reader is here for the mechanism; preface drama is friction. Replace with a direct statement of the structure: _\"A column header has four slot positions for additional UI: …\"_.\n- **Anthropomorphism of inanimate code.** Gives UI parts intentions, conflicts, or effort. _\"fighting the label for space\"_, _\"the slots compete\"_, _\"the header expects\"_, _\"the column wants to be wider\"_. Replace with literal mechanics — _\"the slot is positioned outside the label's flex flow\"_ beats _\"the slot doesn't fight the label for space\"_. If the literal mechanic is unclear, the metaphor was hiding that the writer didn't know.\n- **Vibes enumeration.** Lists capabilities as atmospheric color rather than as commitments to coverage. _\"sort the column, open a filter menu, expose a resize handle, surface a 'Pin to left' affordance\"_ — reads like marketing. Either commit to which the page covers (and link out for the rest), or replace the list with the structural fact behind it (_\"Any UI that mounts into one of these four slots\"_). A list of nice-sounding capabilities the page won't actually teach is filler.\n- **\"Rarely just X\" / \"More than just X\" framings.** Opening with a denial of the obvious in order to motivate the topic. _\"A column header is rarely just a label\"_, _\"This isn't just a placeholder\"_, _\"Sticky columns are more than a CSS trick\"_. The reader didn't claim the thing was just X; the denial creates a fake premise the page then dismantles. Drop the denial and state what the thing is.\n\n## Triage rule\n\nIf a page exhibits multiple smells, fix the opening first. A confused opening cascades: every section after it is read through the wrong lens, so smells later in the page often dissolve once the lead is right.\n",".codex/skills/docs-guidance/references/example-craft.md":"# Example Craft\n\nRules for the code examples themselves — how to construct them so they teach honestly without hiding the lifecycle, state ownership, or variability that real usage will hit.\n\nConsult this file when writing or auditing the code blocks inside a doc page (live demos, recipe snippets, illustrative JSX).\n\n## Production-Safe Defaults\n\nExamples should model the safe version, not a shortened one that hides the real constraints.\n\n- Use stable instances for stateful objects (no inline `new X()` in render).\n- Use stable identities (`key`, `id`) when items can move, persist, or be restored.\n- Include required cleanup, cancellation, or abort handling when async work is shown.\n- Keep generated data schemas deterministic.\n- Keep persistence examples clear about what is and is not saved.\n- Keep styling examples attached to the right layer: local content styling, wrapper styling, component overrides, or global CSS.\n- Avoid examples that are only short because they hide the lifecycle or state-ownership rule.\n\n## Demo Data Craftsmanship\n\nThe shape of the data matters as much as the code that processes it.\n\n- **Variable-shape data for dynamic-feature examples.** When demonstrating something that handles schema variability, the example rows must actually vary — some rows with extra fields, some without. Uniform example rows silently hide the question the feature exists to answer.\n- **Realistic field counts.** Three uniform columns make every feature look easy; six fields with mixed types stress the layout the same way real usage will.\n- **Schemas the reader recognises.** Use product/order/user shapes the reader has seen before. Resist the urge to invent fictional domains that need their own explanation.\n\n## Identifier Discipline Within a Doc\n\n- **Resolve identifier conflicts.** When you introduce a function, type, or prop name, scan the rest of the page for collisions. Two helpers named `fieldsFromRows` with different signatures in the same doc is a real confusion source. Rename one to disambiguate (`fieldsToShow`, `runtimeFields`, etc.).\n- **Inline helpers vs. canonical helpers.** When an example uses a slim inline helper because the canonical full helper would be overkill, name it differently so the reader can tell which one they're looking at.\n\n## Framing Tone\n\nThe words around the code carry the design's intent. Defensive or apologetic phrasing makes intentional choices look like missing features.\n\n- **Defensive to positive.** When the library deliberately doesn't ship a helper, reframe the absence as a benefit: _\"Field scanning lives in your app code so the rules stay obvious next to the table\"_ beats _\"The package does not inspect your row shape for you\"_.\n- **No apologies for design choices.** If the one-shot nature of a feature is intentional, say \"intentionally one-shot\" — not \"currently does not re-fetch\" (which sounds like a missing feature).\n- **Acknowledge limitations honestly.** When a constraint really is a limitation, name it as one and point at the workaround: _\"Offset placeholders cannot be distinguished from real rows. Use schema-compatible placeholders or backend-provided column metadata.\"_ Honest framing beats both apology and silence.\n\n## Component-Example Style\n\nPatterns that recur in component-library docs.\n\n- **`className` over wrapping `<div>`.** When a component accepts a `className`, prefer it to wrapping the render result in a styled `<div>`. Wrapping changes the DOM structure and breaks layout assumptions in many table, grid, and list contexts; using `className` keeps the wrapping consistent with non-example usage.\n- **Use the wrapper component the project ships.** If the project ships a shadcn-style wrapper (`DataTableCell`, `DataTableColumn`), use it in examples instead of the unwrapped primitive. The wrapped form is what users will copy; show that path.\n- **Skip the JSX import boilerplate in snippets.** Inline code blocks in the middle of prose are clearer when they show only the JSX under discussion. Save the full `import` list for live blocks that need to run.\n",".codex/skills/docs-guidance/references/page-shapes.md":"# Page Shapes and Section Shapes\n\nTemplates for whole pages (by type) and for recurring section shapes within pages.\n\nConsult this file when writing a new page from scratch, restructuring an existing page, or naming/ordering the sections of a page that already has its content roughed in.\n\n## Page Shapes by Type\n\n### Concept pages\n\nFor pages that introduce a single concept or primitive (a model, a hook, a low-level component). The audience is developers learning the primitive itself.\n\nIf the page describes a feature with a user-facing surface — grouping, filtering, drag-and-drop, sticky pinning, column reorder, visibility toggles — use the **Feature pages** template instead. Concept template is for pure-developer-facing primitives.\n\n- Open with the reader problem.\n- Explain the concept in ordinary words.\n- Show the data/state/API contract.\n- Show the smallest safe example.\n- Add runtime/lifecycle caveats.\n- Link to one deeper example or adjacent decision.\n\n### Feature pages\n\nFor pages that describe a user-facing capability — grouped rows, filtering, sorting, column reorder, column visibility, sticky pinning, drag-and-drop, expand/collapse. The developer reading the page is configuring something their end user will see or interact with.\n\nThe temptation in feature docs is to lead with the data shape or config object because it's the cleanest thing the writer understands. Resist it — the data shape is what makes the feature work, not what makes the feature interesting to the reader.\n\n- **What the end user sees.** Open with a sentence (or a small \"this is the result\" demo) describing the visible outcome.\n- **What the end user does.** If the feature is interactive, demonstrate the interaction with a live example that includes the UI control the user clicks, toggles, or drags. Static-only demos of interactive features are a smell — see [diagnostics.md](diagnostics.md#feature-pages).\n- **How to wire the interaction.** The API surface that exposes the behavior (model action, hook, prop) paired with the UI piece that triggers it. Treat the UI and the model wiring as one recipe; splitting them hides the half the reader came for.\n- **The data contract or config shape.** Only after the interaction has been shown. The contract is in service of the recipe above it.\n- **Variations.** Local vs remote, simple vs interactive, single-level vs multi-level — each as a sub-section that re-uses the user-behavior-first ordering.\n- **Limits and escape hatches.** Close with what doesn't fit and where to go next.\n\n### Example pages\n\nFor pages that demonstrate a recipe end-to-end.\n\n- First paragraph: \"This example uses X to solve Y.\"\n- `## APIs used` — short list grouped by job, not exhaustive reference.\n- Runnable example code.\n- Closing section named `## Notes`, `## When this doesn't fit`, `## When to outgrow this shape`, or `## What this leaves out`.\n\n### Troubleshooting and performance pages\n\n- Lead with symptoms and decision rules.\n- Prefer tables mapping symptom → fix.\n- Avoid implying users should tune defaults without a concrete symptom.\n\n### Installation pages\n\n- State what gets installed or generated.\n- Distinguish local editable code from package dependencies.\n- Show the smallest successful setup.\n- Link to the next required setup tasks.\n\n### Migration pages\n\n- State what is not a drop-in replacement.\n- Map old concepts to new concepts.\n- Give a recommended sequence, not a pile of independent facts.\n- End with the core mental-model shift.\n\n## Section Shapes (within pages)\n\nThese shapes recur across multiple page types — use them wherever they fit.\n\n### Limitation-focused section (gotcha-led)\n\nWhen a section exists because of a trap — \"Placeholders\" in a runtime-discovery page, \"Empty responses\" in a remote-data page, \"Schema drift\" in a persistence page:\n\n1. Open with the trap. Describe the bug the section's recipe prevents.\n2. Show the recipe.\n3. Add sidebar advice for cases the recipe still doesn't cover.\n\nExample opening: _\"The field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data.\"_ — then the schema-compatible placeholder recipe.\n\n### Comparison / \"When this does not fit\" section\n\nWhen enumerating alternatives at the end of a page, use **named bullets** with bold term labels:\n\n```markdown\n- **Backend-defined schemas.** Use when the backend already knows the shape.\n- **Statically-known schemas.** Use when the schema is in code but the JSX would be repetitive.\n- **Runtime column-management UI.** Lift the field list into your own state when the user can add or reorder fields after mount.\n```\n\nClose with a one-sentence summary of when the current page's approach _does_ fit.\n\n### Topic-first, alternative-second\n\nNever open a section by recommending the alternative it is meant to compare against. Pattern:\n\n1. Show the section's actual topic.\n2. After the example, add a sidebar paragraph for the alternative — \"When the backend can return column metadata alongside the rows, prefer that.\"\n\n### Parameter / config explanation block\n\nWhen showing what arrives at a render prop, hook, or callback, use bullets per parameter:\n\n```markdown\nThe render function is called with `{ data, model }`:\n\n- `data` is X — it does not change when Y.\n- `model` is Z — available for the rare case where W.\n```\n\nEasier to scan than parallel prose sentences.\n\n### \"How the contract looks\" before the example\n\nWhen behavior depends on a contract (data shape, config shape, event payload), render the shape inline before the runnable example:\n\n````markdown\nThe render function receives:\n\n```ts\n{\n  data: readonly Row[]\n  model: DataModelHandle<Row>\n}\n```\n\nThen the example…\n````\n\nThe shape grounds the example. Without it, the reader has to reverse-engineer the contract from the example body.\n","AGENTS.md":"# AGENTS.md\n\nRepo-specific guidance for agents working in `react-virtuoso`.\n\nUse this file for repo-specific navigation, validation, and documentation rules only. Keep it short, concrete, and limited to information that helps an agent make better decisions in this repository.\n\nIf a rule here appears to conflict with `package.json`, the actual filesystem, or a more specific instruction file in scope, trust the repo state and the more specific instruction.\n\n## High-Signal Rules\n\n- This is a `pnpm` workspaces monorepo. Prefer running commands from the repo root unless you need package-local scope.\n- Root `pnpm lint` does **not** run type checking. Run `pnpm typecheck` separately when you need type validation.\n- Internal working artifacts belong in Notion, not in repo markdown files.\n  - Use Notion for PRPs, plans, clarifications, execution notes, verification notes, prompts, investigations, reports, audits, and exported findings that are not product docs.\n  - Do not create or keep repo markdown artifacts in folders like `plans/`, `prompts/`, `reports/`, or similar ad hoc locations.\n  - Create new internal artifacts from the shared Notion template at `https://www.notion.so/3461834d8eff81318a4cccaf19b0de51` instead of creating free-form pages from scratch.\n  - The template's visual settings such as `Small text` and `Full width` are maintained manually in Notion. Preserve them by duplicating the template when possible.\n  - If the user asks for a PRP workflow, create or update the artifact in Notion instead of creating `plans/*.md`.\n  - If the user asks for a report or findings export, prefer a Notion page unless they explicitly ask for a repo file.\n  - Set a page icon that matches the artifact type or content when creating the page. Prefer clear, stable icons such as `📗` for PRPs/plans, `📊` for reports, `🔎` for investigations, `✍️` for prompts, and `🛠️` for execution notes.\n  - If an existing markdown file looks ambiguous, ask before deleting or migrating it.\n- Never use `data-testid` as a styling hook. It is reserved for tests. Use semantic attributes such as `data-table-element-role` instead.\n\n## NPM Registry Queries\n\nThis repo uses `devEngines.packageManager` with `\"onFail\": \"error\"` in the root `package.json`. `pnpm info` and `pnpm view` can fail because they delegate to npm and npm 11 rejects the request in this setup.\n\nUse npm from outside the repo instead:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Workspace Layout\n\n### Main packages\n\n- `packages/data-table` - virtualized data table package; currently the most active package in this repo\n- `packages/react-virtuoso` - main virtualization library\n- `packages/message-list` - chat/message list package\n- `packages/masonry` - masonry layout package\n- `packages/gurx` - reactive state library used by older Virtuoso internals\n- `packages/reactive-engine-*` - lower-level engine packages used by `data-table`\n\n### Apps and examples\n\n- `apps/virtuoso.dev` - Astro/Starlight docs site\n- `examples` - separate workspace for shared Ladle/integration examples\n- `packages/react-virtuoso/examples` - example pages used by `packages/react-virtuoso/e2e`\n\n## Plugin Distribution\n\n- This repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n- After merge, public Codex plugin install is:\n\n  ```bash\n  codex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\n  codex plugin add virtuoso-skills@virtuoso\n  ```\n\n- Source skill files live at `packages/virtuoso-skills/skills/<name>/SKILL.md`.\n- Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly. They are generated by `pnpm build:skills`.\n- Do not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Commands\n\n### Root\n\n- `pnpm build` - build all workspaces\n- `pnpm build:skills` - regenerate skill references and public skill mirrors\n- `pnpm validate:skills` - validate Claude and Codex plugin manifests\n- `pnpm typecheck` - run type checking across workspaces\n- `pnpm lint` - run package lint scripts across workspaces\n- `pnpm format` - format with `oxfmt`\n- `pnpm format:check` - check formatting with `oxfmt`\n- `pnpm test` - run workspace test scripts\n- `pnpm e2e` - run workspace e2e scripts\n- `pnpm lint:md` / `pnpm lint:md:fix` - markdown lint / fix\n- `pnpm ci` - full CI sequence: setup, build, typecheck, lint, markdown lint, test, e2e\n- `pnpm dev:docs` - start the docs site\n- `pnpm changeset-add` - add a changeset\n\n### `packages/data-table`\n\n- `pnpm build` - `tsc && vite build`\n- `pnpm typecheck` - `tsgo -b --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - `vitest run --browser.headless`\n- `pnpm check` - `format:check + lint + typecheck`\n- `pnpm dev` - run Ladle serve mode\n- `pnpm dev:build` / `pnpm dev:preview` - Ladle build / preview\n\n### `packages/react-virtuoso`\n\n- `pnpm build` - Vite build\n- `pnpm typecheck` - `tsgo --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - Vitest\n- `pnpm test:watch` - Vitest watch\n- `pnpm e2e` - Playwright\n- `pnpm ladle` - preview examples in Ladle\n\n### `apps/virtuoso.dev`\n\n- `pnpm dev` - Astro dev server\n- `pnpm build` - `shadcn build && astro build`\n- `pnpm lint` - `oxlint --type-aware --type-check && astro check`\n- `pnpm format` / `pnpm format:check` - `oxfmt` plus Prettier for `.astro`\n\n## Documentation Workflow\n\nThe docs site contains generated package docs. Do not edit the generated content under:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nEdit the source docs in the package instead:\n\n- `packages/data-table/README.md` and `packages/data-table/docs/*.md`\n- `packages/react-virtuoso/README.md` and `packages/react-virtuoso/docs/*.md`\n- `packages/message-list/README.md` and `packages/message-list/docs/*.md`\n- `packages/masonry/README.md`\n- `packages/gurx/README.md`\n\nAfter product docs changes:\n\n- run `pnpm lint:md` from the repo root\n- if docs-site content or registry code changed, run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n\n## Validation Guidance\n\nChoose the narrowest validation that matches the change:\n\n- `packages/data-table` code changes: start with `pnpm check && pnpm test` in `packages/data-table`\n- `packages/react-virtuoso` code changes: run `pnpm lint && pnpm typecheck && pnpm test`, add `pnpm e2e` when behavior changes\n- docs-only markdown changes: run `pnpm lint:md`\n- docs site or registry changes: run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n- broad cross-package changes: run root `pnpm typecheck && pnpm lint && pnpm test`\n\n## Architecture Notes\n\n- `packages/react-virtuoso` uses the custom `urx` stream/state system under `packages/react-virtuoso/src/urx`. If you touch its internals, follow existing `*System.ts` and `react-urx/` patterns.\n- `packages/data-table` is a separate package built on the `reactive-engine-*` workspaces. When working there, prefer matching existing engine/cell/stream patterns instead of borrowing directly from `react-virtuoso` internals.\n\n## Git Hooks\n\nThis repo uses `lefthook`. Pre-commit hooks format and lint staged files. If you need to skip them for a WIP commit:\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: ...\"\n```\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## NPM Registry Queries\n\nThis project uses `devEngines.packageManager` with `\"onFail\": \"error\"` in `package.json`, which causes `pnpm info` and `pnpm view` to fail because they delegate to npm, and npm 11 rejects the request. To check package versions, run npm from outside the project directory:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Build Commands\n\nThis is a pnpm workspaces monorepo. Run commands from the root or within specific workspace packages.\n\n### Root-level commands\n\nRun from repository root for all packages:\n\n- Build all: `pnpm build`\n- Lint all (includes type checking): `pnpm lint`\n- Format all: `pnpm format` (oxfmt)\n- Format check: `pnpm format:check`\n- Test all: `pnpm test`\n- E2E tests all: `pnpm e2e`\n- Markdown lint: `pnpm lint:md` / fix: `pnpm lint:md:fix`\n- Full CI: `pnpm ci` (setup, build, lint, lint:md, test, e2e)\n- Release: `pnpm release` (build + publish with changesets)\n- Add changeset: `pnpm changeset-add`\n- Dev docs site: `pnpm dev:docs`\n\n### react-virtuoso package (packages/react-virtuoso/)\n\n- Build: `pnpm run build` (uses vite)\n- Test: `pnpm run test` (vitest)\n- Test watch: `pnpm run test:watch`\n- Run single test: `pnpm vitest <test-file-path>` or `pnpm vitest -t \"<test-name>\"`\n- E2E tests: `pnpm run e2e` (playwright)\n- Lint (includes type checking): `pnpm run lint`\n- Format: `pnpm run format` (oxfmt)\n- Format check: `pnpm run format:check`\n- Dev/preview examples: `pnpm run ladle` (launches Ladle server for browsing examples/ folder)\n\n### virtuoso.dev docs app (apps/virtuoso.dev/)\n\n- Dev server: `pnpm run dev`\n- Build: `pnpm run build`\n- Format: `pnpm run format` (oxfmt + Prettier for .astro files)\n\nAfter docs changes: run `pnpm lint` from the app directory or root.\n\n**IMPORTANT - Documentation Locations:**\n\n**DO NOT EDIT** the following auto-generated directories:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nThese are auto-synced from source files + TypeDoc API via the `docsSync` integration. Any edits will be overwritten.\n\n**To edit package documentation**, modify the source files in each package:\n\n- **data-table**: `packages/data-table/docs/*.md`\n- **react-virtuoso**: `packages/react-virtuoso/README.md` or `packages/react-virtuoso/docs/*.md`\n- **masonry**: `packages/masonry/README.md` or `packages/masonry/docs/*.md`\n- **gurx**: `packages/gurx/README.md` or `packages/gurx/docs/*.md`\n- **message-list**: `packages/message-list/README.md` or `packages/message-list/docs/*.md`\n\n## Plugin Distribution\n\nThis repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n\nEdit source skill content in `packages/virtuoso-skills/skills/<name>/SKILL.md`. Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly; regenerate them with:\n\n```bash\npnpm build:skills\n```\n\nAfter merge, public Codex plugin install uses:\n\n```bash\ncodex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\ncodex plugin add virtuoso-skills@virtuoso\n```\n\nDo not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Monorepo Structure\n\n```text\npackages/\n  react-virtuoso/    - Main virtualization library\n  gurx/             - urx state management (fork/variant)\n  masonry/          - Masonry layout component\n  message-list/     - Chat/message list component\n  tooling/          - Shared build tooling\n\napps/\n  virtuoso.dev/     - Starlight/Astro documentation site\n\nexamples/            - Ladle stories for testing/development\n```\n\n## Architecture\n\n### State Management: urx System\n\nThe codebase uses **urx**, a custom reactive state management system built on streams/observables. Core concepts:\n\n- **Systems**: Stateful data-processing machines composed of streams\n- **Streams**: Can be stateless (signals) or stateful (depots that persist values)\n- **Depots**: Implicit state maintained in stateful streams, transformers (combineLatest), or operators (withLatestFrom, scan)\n- **Input/Output**: Systems receive input via input streams, process via transformers/operators, emit via output streams\n\nKey urx files: `src/urx/` directory contains:\n\n- `system.ts` - System creation and composition\n- `streams.ts` - Stream primitives\n- `pipe.ts` - Stream operators and transformers\n- `actions.ts` - Publishing/emitting\n- `transformers.ts` - Stream transformation utilities\n\n### Component Architecture\n\nThe virtualization logic is split into modular systems in `src/`:\n\n**Core Systems:**\n\n- `listSystem.ts` - Composes all feature systems into the main list system\n- `sizeSystem.ts` - Tracks and manages item sizes (critical for variable-height items)\n- `listStateSystem.ts` - Manages visible item ranges and scrolling state\n- `domIOSystem.ts` - DOM measurements and interactions\n\n**Feature Systems:**\n\n- `groupedListSystem.ts` - Grouped lists with sticky headers\n- `scrollToIndexSystem.ts` - Programmatic scroll positioning\n- `followOutputSystem.ts` - Auto-scroll for chat/feed UIs\n- `initialTopMostItemIndexSystem.ts` - Initial scroll position\n- `scrollSeekSystem.ts` - Placeholder rendering during fast scrolling\n- `windowScrollerSystem.ts` - Window-scrolling mode\n- And many more in `src/*System.ts` files\n\n**React Integration:**\n\n- `react-urx/` - Bridges urx systems to React components\n- `Virtuoso.tsx` - Main list component\n- `VirtuosoGrid.tsx` - Grid layout component\n- `TableVirtuoso.tsx` - Table virtualization component\n- Component interfaces in `component-interfaces/`\n\n### Size Calculation\n\nVariable-sized items work automatically via `sizeSystem.ts`:\n\n- Uses ResizeObserver for measurements\n- Maintains size ranges and estimates\n- No manual height specification needed\n- `correctItemSize()` utility in `utils/` handles size corrections\n\n### E2E Testing\n\nE2E tests in `packages/react-virtuoso/e2e/`:\n\n- Test files: `*.test.ts` (Playwright tests)\n- Example pages: `examples/*.tsx` (rendered in browser for tests)\n- Use Ladle (`pnpm run ladle`) to preview examples during development\n\n## Code Style\n\n- Never use `data-testid` attributes for CSS styling selectors. `data-testid` is reserved for testing only. Use `data-table-element-role` or other semantic data attributes for styling hooks.\n- TypeScript with strong typing; avoid `any`\n- oxfmt: 140 char width, single quotes, no semicolons\n- Naming: camelCase for variables/functions, PascalCase for components\n- Imports: React first, external libs, then internal modules\n- Functional components with hooks preferred\n- Use urx system patterns for state management\n- Error handling: prefer early returns\n\n## Markdown Style Guide\n\nWhen writing or editing markdown documentation:\n\n### Formatting Rules\n\n- Headings: ATX-style (`# Heading` not `Heading\\n=======`)\n- Code blocks: Always use fenced blocks with language specifiers\n\n```typescript\nconst foo = 'bar'\n```\n\n- Lists: Use `-` for unordered lists, indent nested items by 2 spaces\n- Emphasis: Use `_single underscore_` for emphasis, `**double asterisk**` for strong\n- Links: Prefer inline links `[text](url)` for readability\n\n### Content Guidelines\n\n- Start with clear, descriptive headings (H1 for title, H2 for major sections)\n- Use code blocks for all code examples, terminal commands, and file paths\n- Include language identifiers in fenced code blocks (`typescript`, `bash`, `json`)\n- Break long paragraphs into shorter ones (3-5 sentences max)\n- Use tables for structured data comparison\n- Add blank lines before and after headings, lists, code blocks, and tables\n\n### Special Cases\n\n- Inline HTML allowed for badges, complex layouts, or special formatting\n- Bare URLs allowed in reference sections and changelogs\n- Line length not enforced (practical for existing docs)\n- Multiple H1 headings allowed (document sections)\n\n### Linting\n\n- Run `pnpm lint:md` to check markdown files\n- Run `pnpm lint:md:fix` to auto-fix issues\n- Pre-commit hooks automatically lint staged .md files\n- Configuration: `.markdownlint.json` and `.markdownlintignore`\n- If necessary, use `markdownlint` CLI directly, but prefer pnpm scripts\n\n## Code Change Checklist\n\nAfter making code changes, run these commands to verify quality:\n\n### Required (always run)\n\n- `pnpm lint` - Lint and type check (oxlint --type-aware --type-check)\n- `pnpm format` - Format code with oxfmt\n- `pnpm test` - Run unit tests (vitest)\n\n### Conditionally Required\n\n- `pnpm lint:md` - If editing markdown files\n- `pnpm lint:md:fix` - Auto-fix markdown issues\n- `pnpm run e2e` - For UI/behavior changes (Playwright tests)\n- `pnpm run ladle` - To visually inspect component changes\n\n### Quick Full Validation\n\n- `pnpm ci` - Run complete CI pipeline (setup, build, lint, lint:md, test, e2e)\n- `pnpm format:check` - Check if files are formatted without modifying them\n\n### Fixing Issues\n\nFormat issues are auto-fixed by `pnpm format`. oxlint issues must be fixed manually. Configure your editor to:\n\n- Format on save using oxfmt (140 char width, single quotes, no semicolons)\n- Show oxlint warnings/errors\n\nPre-commit hooks will block commits if lint (which includes type checking) fails.\n\n## Development Workflow\n\n1. Make changes in `packages/react-virtuoso/src/`\n2. Run `pnpm format && pnpm lint && pnpm test`\n3. Check examples with `pnpm run ladle` if UI changes\n4. Run `pnpm e2e` for end-to-end validation if needed\n5. Add changeset with `pnpm changeset-add` for versioned changes\n\n## Git Hooks\n\nThis project uses [lefthook](https://github.com/evilmartians/lefthook) for git hooks.\n\n### Pre-commit Checks\n\nOn every commit, the following checks run automatically on staged files:\n\n- **Code formatting**: Formats `.ts/.tsx/.js/.jsx` files with oxfmt and `.astro` files with prettier; changes are auto-staged via `stage_fixed`\n- **Markdown linting**: Validates .md files with markdownlint\n- **Code linting**: Validates code files with oxlint\n\n### Skipping Hooks\n\nIf you need to skip hooks (e.g., WIP commits):\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: work in progress\"\n# Or use git commit --no-verify (not recommended)\n```\n\n### Hook Management\n\n- Configuration: `lefthook.json`\n- Install hooks: `pnpm exec lefthook install`\n- Uninstall hooks: `pnpm exec lefthook uninstall`\n- Run manually: `pnpm exec lefthook run pre-commit`\n","packages/data-table/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Development Commands\n\n- `pnpm build` - Build the library using TypeScript compiler and Vite\n- `pnpm lint` - Run linting with oxlint (type-aware)\n- `pnpm typecheck` - Run TypeScript type checking without emitting files\n- `pnpm test` - Run tests with Vitest (node and browser projects)\n- `pnpm dev` - Start Ladle development server for component stories\n- `pnpm docs:build` - Generate TypeDoc documentation\n- `pnpm docs:serve` - Serve generated documentation\n- `pnpm format` - Format code with oxfmt\n- `pnpm format:check` - Check formatting without modifying files\n\n## After Making Changes\n\nRun these commands after modifying code:\n\n1. `pnpm format` - Format modified files\n2. `pnpm lint` - Check for lint errors and fix any issues\n3. `pnpm typecheck` - Verify no TypeScript errors\n4. `pnpm test` - Run tests when changes affect component logic, state management, or virtualization behavior\n\nGit hooks (via Lefthook) automatically run formatting and linting on pre-commit, and typecheck + tests on pre-push.\n\n## Architecture Overview\n\nThis is a React virtualized data table component with both column and row virtualization. The architecture is based on a reactive state management system using `@virtuoso.dev/reactive-engine`.\n\n### Core Components\n\n- **VirtuosoDataTable** (`src/VirtuosoDataTable.tsx`) - Main React component that renders the virtualized data table\n- **VirtualizedTableContent** (`src/VirtualizedTableContent.tsx`) - Renders the table content with row and column virtualization\n- **Row** (`src/Row.tsx`) - Individual row renderer\n- **Column** (`src/Column.tsx`) - Column renderer with virtualization\n- **Cell** (`src/Cell.tsx`) - Cell renderer\n- **ColumnHeader** (`src/ColumnHeader.tsx`) - Column header renderer\n- **Data State** (`src/data.ts`) - Holds data cells (`data$`, `context$`, `totalCount$`)\n\n### State Management Architecture\n\nThe component uses a realm-based reactive system with cells:\n\n- **Cells** - Hold state values (e.g., `data$`, `context$`, `totalCount$`)\n- **Realm** - Manages the reactive graph and subscriptions\n\nKey state modules:\n\n- `src/data.ts` - Core data cells\n- `src/dom.ts` - DOM measurements and scroll handling\n- `src/row-state.ts` - Row rendering state management and visible row computation\n- `src/column-state.ts` - Column virtualization state\n- `src/sizes.ts` - Size tree management for virtualization\n- `src/state-flags.ts` - Scroll direction and bottom detection\n\n### Virtualization System\n\n- **Size Tree** (`src/sizing/AATree.ts`) - AA tree implementation for efficient size tracking\n- **Offset Calculations** (`src/sizing/` directory) - Handles item positioning and measurements\n- **Resize Observer** - Tracks item size changes dynamically\n- **Viewport Management** - Renders only visible items with buffer zones\n\n### Scroll Behavior Management\n\n- **Scroll-to-Row** (`src/scroll-to-row.ts`) - Handles programmatic scrolling to specific rows\n- **Smooth Scrolling** (`src/useSmoothScroll.ts`) - Custom smooth scroll implementation\n- **Mobile Safari Fixes** (`src/reverse-scroll-fix.ts`) - Platform-specific scroll behavior corrections\n\n### Testing Infrastructure\n\nTests are organized into two Vitest projects:\n\n- **Node Tests** - Pure logic tests in `tests/node/` and `src/sizing/tests/node/`\n- **Browser Tests** - Component tests using Playwright browser in `tests/browser/` and `src/tests/browser/`\n  - Setup file: `src/tests/browser/setup.ts`\n- **Ladle Stories** - Component stories in `src/_stories/` for development and visual testing\n- **Testing Context** - `VirtuosoDataTableTestingContext` for controlled test environments\n\n### Test Writing Guidelines\n\n**Use exact assertions, not ranges.** Tests must verify precise expected values:\n\n- Use `toBe(9)` not `toBeGreaterThan(0)` or `toBeLessThan(10)`\n- Use `toHaveLength(3)` not `toHaveLength(expect.any(Number))`\n- Calculate expected values from test constants (e.g., `Math.ceil((CONTAINER_HEIGHT - HEADER_HEIGHT) / ROW_HEIGHT)`)\n\n**Every assertion must be derivable from the test setup.** If you set `COLUMN_COUNT = 6` and `CONTAINER_WIDTH = 300` with `COLUMN_WIDTH = 150`, then assert exactly 2 visible columns, not \"less than 6\".\n\n**Avoid false-positive-prone patterns:**\n\n- `toBeGreaterThan(0)` - passes even when value is wrong\n- `toBeLessThan(TOTAL)` - passes even when virtualization is broken\n- `not.toBe(previousValue)` without asserting the new expected value\n\n**Correct pattern:**\n\n```typescript\nconst visibleColumns = Math.floor(CONTAINER_WIDTH / COLUMN_WIDTH)\nexpect(cells.length).toBe(visibleColumns)\n```\n\n### API Surface\n\nThe component is controlled through the reactive engine rather than a component ref API:\n\n- `engineRef` / `engineId` identify the table instance\n- `scrollToRow$` and `scrollIntoView$` handle programmatic scrolling\n- `scrollLocation$` exposes current scroll position information\n- `cancelSmoothScroll$` cancels the current smooth scroll animation\n\n### Key Design Patterns\n\n1. **Reactive State Management** - All state changes flow through the reactive engine reactive system\n2. **Declarative Data with Reactive Control** - Data is passed declaratively via prop; table operations use engine streams\n3. **Performance Optimization** - Only renders visible items with efficient size caching\n4. **Cross-platform Compatibility** - Handles browser-specific scroll behavior differences\n5. **Internal State Ownership** - The table manages its own state internally. External code does not lift state out of the table and pass it back in as props. Instead, the table's internal state is accessible and controllable through an imperative \"remote control\" pattern (e.g., ref methods, command channels). This avoids the lifted-state antipattern where the developer extracts table state, transforms it, and feeds it back, which creates extra render cycles and race conditions.\n\n## Research Guidelines\n\n- **Must not use articles, docs, or source code from** TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid.\n- **Must not reference external libraries** like TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid, or similar - their architecture is irrelevant to this codebase\n- Focus exclusively on understanding and extending the existing patterns in this repository\n","packages/data-table/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2FREADME.md -->","packages/data-table/docs/1.installation/01.shadcn.md":"---\ntitle: Install With shadcn/ui\ndescription: Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.\nsidebar:\n  label: Shadcn Install\n---\n\nThe shadcn registry copies a `DataTable` wrapper into `@/components/ui/data-table`. The wrapper re-exports the headless engine from `@virtuoso.dev/data-table` and ships pre-styled `DataTable`, `DataTableColumn`, `DataTableColumnHeader`, and `DataTableCell`. You edit the wrapper; the headless package stays installed as a dependency.\n\n## Prerequisites\n\n- A React application with TypeScript\n- Tailwind CSS configured\n- shadcn/ui initialized in the project\n- Path aliases resolvable from the root config shadcn reads. If a project uses a solution-style root `tsconfig.json` that only contains `references`, mirror the `@/*` path mapping there or shadcn may create a literal `@/components/...` directory instead of writing into `src/components/...`.\n\n## Add the registry component\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table\n```\n\nThe command writes the wrapper to `@/components/ui/data-table` and imports `@virtuoso.dev/data-table/styles.css` (structural layout defaults). No extra CSS setup is needed.\n\nFeature-specific wrapper components use the same GitHub registry address. For example, add the resize handle when you enable column resizing:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nAdd the sort header button when sortable column headers should dispatch model actions:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe hosted registry URL still works if you prefer the existing virtuoso.dev registry endpoint:\n\n```bash\nnpx shadcn@latest add https://virtuoso.dev/r/data-table.json\n```\n\n## Icon library\n\nThe wrapper uses [`lucide-react`](https://lucide.dev/) for the loading spinner, error icon, drag grip, and sort button icons. The `npx shadcn add` command installs `lucide-react` as a dependency on first run.\n\nshadcn's `iconLibrary` field in `components.json` only rewrites icon imports for the canonical shadcn registry — it does not touch components from third-party registries like ours. If your project uses a different icon set (`@radix-ui/react-icons`, `phosphor-react`, etc.) and you want the table icons to match, open the installed files under `@/components/ui/data-table` and swap the lucide imports for your library's equivalents. The icons appear in:\n\n- `index.tsx` — `Loader2` (spinner), `AlertCircle` (error state)\n- `column-reorder/reorder-grip.tsx` — `GripVertical` (drag handle)\n- `column-sort/sort-header-button.tsx` — `ArrowUpDown`, `ArrowUp`, `ArrowDown` (sort state)\n\n## Basic styled table\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Audio' | 'Office' | 'Peripherals'\n  price: number\n  stock: number\n}\n\nexport const products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319, stock: 9 },\n]\n```\n\n## Next steps\n\nThe wrapper picks up your shadcn theme automatically (border radius, color tokens, typography). Edit `@/components/ui/data-table` directly for custom row hover states, sticky backgrounds, or denser spacing.\n\nThe two required setup steps after installation are [data model](/data-table/data-model/) and\n[columns](/data-table/columns/). For an inventory of what `npx shadcn add` actually wrote into\nyour project and how to edit it, see [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/);\nfor per-instance visual changes, see [Styling](/data-table/customization/styling/).\n\nRemote models, sticky columns, and instance control all import from `@virtuoso.dev/data-table` directly while the wrapper continues to provide the styled `DataTable`.\n","packages/data-table/docs/1.installation/02.headless.md":"---\ntitle: Install The Headless Package\ndescription: Use the headless Virtuoso data table package directly when you need full control over styling and composition.\nsidebar:\n  label: Headless Install\n---\n\nThe headless package gives you `VirtuosoDataTable`, `Column`, `ColumnHeader`, and `Cell` without any preset styling. Pick it when you already have a design system, use non-Tailwind styling, or need fully custom markup around the table.\n\n## Install the package\n\n```bash\npnpm add @virtuoso.dev/data-table\n```\n\n`npm`, `yarn`, and `bun` work too.\n\n## Import structural styles\n\nThe package ships layout defaults (column sizing, box model) in a `@layer virtuoso` block. Import the stylesheet once in your app's CSS or entry point:\n\n```css\n@import '@virtuoso.dev/data-table/styles.css';\n```\n\n```typescript\nimport '@virtuoso.dev/data-table/styles.css'\n```\n\nThe `@layer virtuoso` wrapping puts these rules at the lowest cascade priority, so any plain CSS or Tailwind utility class overrides them.\n\n## Minimal headless example\n\n```tsx live wide file=App.tsx\nimport { Cell, Column, ColumnHeader, VirtuosoDataTable, localModel } from '@virtuoso.dev/data-table'\n\nimport { rows } from './data'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTable\n      model={model}\n      style={{\n        height: 360,\n        border: '1px solid var(--border)',\n        borderRadius: 16,\n        overflow: 'hidden',\n      }}\n    >\n      <Column field=\"name\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Product</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"category\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Category</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"price\">\n        <ColumnHeader>\n          {() => (\n            <div\n              style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}\n            >\n              Price\n            </div>\n          )}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div\n              style={{\n                padding: '0 12px',\n                height: 44,\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'flex-end',\n                fontVariantNumeric: 'tabular-nums',\n              }}\n            >\n              {currency.format(Number(cellValue))}\n            </div>\n          )}\n        </Cell>\n      </Column>\n    </VirtuosoDataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## Next steps\n\nThe headless package exposes the full surface area: local and remote data models, instance control, scroll control, and testing utilities. Continue with [data model](/data-table/data-model/) and [columns](/data-table/columns/).\n","packages/data-table/docs/2.data-model/01.local-data-model.md":"---\ntitle: Local Data Model\ndescription: Use localModel for in-memory rows, derived views, persistence, and grouped data.\nsidebar:\n  label: Local Data Model\n---\n\nWhen the rows are already in the browser — a hardcoded array, React state, a client-side collection mutated by user actions — `localModel()` is the model that wraps them. It holds the array in memory and exposes actions that mutate or derive the rows the table displays.\n\n```typescript\nconst model = localModel({ data: products })\n```\n\nPass the model to the table:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  {/* columns */}\n</DataTable>\n```\n\n## Where to keep the model\n\nThe model holds reactive state — engine subscriptions, the pipeline's stage cache, persisted action payloads. Recreating it discards that state, so the model instance must outlive renders. Two patterns cover almost every case:\n\n**Module scope** — when the dataset is static and the table is a singleton on the page. This is the simplest possible form and matches how most examples in these docs are written.\n\n```tsx\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport function ProductsTable() {\n  return <DataTable model={model}>{/* columns */}</DataTable>\n}\n```\n\nThe model is shared by every render and every mount of `ProductsTable`. Filter and sort state survives navigations away from the page — usually desirable; occasionally not. Initial config can't depend on props because there's no component scope yet.\n\n**`useState` with lazy init** — inside the component, for everything else: multiple table instances on the same page, initial data sourced from props, dynamic mount/unmount.\n\n```tsx\nconst [model] = useState(() => localModel<Product>({ data: initialProducts }))\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Reach for this whenever the model can't live in module scope.\n\nAvoid `useMemo(() => localModel(...), [])` for this purpose. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which would silently reset the model's subscriptions and state. `useState` is the only hook that guarantees retention.\n\n## Pipelines\n\nSource rows rarely match what a table should actually display. A search box trims them down, a header click reorders them, a toolbar groups them by category. A **pipeline** is the ordered chain of stages that derives the rendered rows from the source array — each stage takes the previous stage's output, transforms it, and hands it to the next one.\n\nTwo properties keep the pipeline predictable as actions land:\n\n- The order is **declared once**, so every action lands in a predictable place instead of racing against the others.\n- Each stage's output is **cached**, so an unrelated stage doesn't rerun when only its successor changes.\n\nDeclare the stage order on the model, then attach each handler to the stage it belongs to:\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter', 'sort'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      handler: ({ data, payload }) => data.filter((row) => row.category === payload),\n    },\n    sort: {\n      stage: 'sort',\n      handler: ({ data }) => data.toSorted((a, b) => a.name.localeCompare(b.name)),\n    },\n  },\n})\n```\n\nStage names are open labels — `filter`, `sort`, and `group` are the conventional ones, but you can split work across whatever stages your table needs (`enrich`, `paginate`, `dedupe`). Inside each handler, `data` is whatever the previous stage produced: filter sees the source rows, sort sees the filtered rows, a later group stage would see the sorted ones.\n\nWhen the `filter` payload changes, the filter stage and the later sort stage both rerun. When only `sort` changes, the cached filter result is reused — the difference matters once filtering or grouping touches thousands of rows.\n\n## Source mutators\n\nAn action declared without a `stage` mutates the backing rows. Source mutators invalidate the full pipeline because every derived stage depends on the new source.\n\n```typescript\nconst model = localModel({\n  data: products,\n  actions: {\n    renameProduct: {\n      handler: ({ source, payload }) => source.map((row) => (row.id === payload.id ? { ...row, name: payload.name } : row)),\n    },\n  },\n})\n\nmodel.send({ action: 'renameProduct', payload: { id: 'SKU-001', name: 'Desk Pro' } })\n```\n\n`model.send({ action, payload })` is how buttons, inputs, effects, and table controls dispatch actions. The action name picks the handler; the payload is whatever your handler expects.\n\n## React state updates\n\nWhen rows arrive from React state, a websocket, or a parent workflow, push the next row set into the model instead of recreating it — see [Where to keep the model](#where-to-keep-the-model) for why the instance must stay stable.\n\n```tsx\nconst [model] = React.useState(() => localModel<Product>({ data: initialProducts }))\n\nReact.useEffect(() => {\n  model.setData?.(products)\n}, [model, products])\n```\n\nFor grouped rows, `model.setData(nextRows, nextGroups)` updates rows and markers together.\n\nIf the row schema changes intentionally, remount the table with a new `key` so runtime columns can discover the new first result. See [Runtime Columns](/data-table/columns/runtime-columns/#rediscovering-columns).\n\n### Updating rows in place\n\n`setData` resets the table's size tracking along with the rows — every row is measured again, which briefly unmounts and remounts visible cells. When a refresh only changes row values — a live price tick, a status flip — and the row set is otherwise identical, `model.updateData(nextRows)` skips that reset:\n\n```tsx\nReact.useEffect(() => {\n  model.updateData?.(products)\n}, [model, products])\n```\n\n`updateData` requires the caller to guarantee a strict contract between `nextRows` and the current rows:\n\n- the row count is unchanged\n- each index still holds the same logical row — no insertions, deletions, or reorders\n- `computeRowKey` returns the same key for each row before and after the update\n- group markers are unaffected — `updateData` never takes a `groups` argument, since group placement is index/level metadata a value-only update can't legitimately change\n- row values that do not determine the row key may change freely\n\nCached sizes are retained as estimates for the new values and corrected by `ResizeObserver` for any row that's actually mounted. If the row count doesn't match, the table falls back to a full reset and logs a dev warning.\n\nA pipeline stage that sorts or filters on a field the update just changed can reorder rows at the same indices, which breaks the \"same logical row per index\" contract and misattributes cached heights to the wrong row. `updateData` on a model with a `pipeline` logs a dev warning for this reason — use `setData` there instead.\n\nA custom model (built directly on the model protocol rather than `localModel`) opts into the same behavior by returning `operation: 'update'` on the `DataResult` it emits.\n\n## Grouping\n\nWhen rows naturally cluster — sales by region, products by category, employees by department — group headers belong between the rows they introduce. The library keeps those headers inside the same flat `data` array rather than nesting rows under each header, so the pipeline, sizing, and virtualization treat them as ordinary rows. A separate `groups` array tells the table which entries are headers and how deeply they nest.\n\n### Static groups\n\nWhen the grouping is computed up front, pass pre-grouped rows and markers to `localModel`:\n\n```tsx\nconst rows = [\n  { label: 'Office' },\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  { label: 'Peripherals' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals' },\n]\n\nconst groups = [\n  { index: 0, level: 0 },\n  { index: 3, level: 0 },\n]\n\nconst [model] = useState(() => localModel({ data: rows, groups }))\n```\n\n`index` points at the group row in `data`. `level` is the nesting depth — `0` for top-level headers, `1` for headers nested under them, and so on.\n\n### Multiple levels\n\nHigher `level` values render deeper and stick under their parent when the user scrolls. A two-level `region → country → city` table declares each region marker at `level: 0` and each country marker at `level: 1`:\n\n```typescript\nconst data = [\n  { groupName: 'North America' },\n  { groupName: 'USA' },\n  { name: 'New York', population: 8_336_000 },\n  { name: 'Los Angeles', population: 3_898_000 },\n  { groupName: 'Canada' },\n  { name: 'Toronto', population: 2_794_000 },\n  { groupName: 'Europe' },\n  { groupName: 'UK' },\n  { name: 'London', population: 8_982_000 },\n]\n\nconst groups = [\n  { index: 0, level: 0 }, // North America\n  { index: 1, level: 1 }, // USA\n  { index: 4, level: 1 }, // Canada\n  { index: 6, level: 0 }, // Europe\n  { index: 7, level: 1 }, // UK\n]\n```\n\nThe table treats `level` as a depth hint, not a tree. There's no `parent` field — a `level: 1` header is rendered as a child of whichever `level: 0` header most recently preceded it in `data`, and sticky headers reserve a row per active level on screen. Skipping levels or reordering markers is allowed; the table still renders every row in the order you provided.\n\n### Computing groups in a pipeline\n\nWhen the grouping dimension is interactive — \"group by category\" toggling to \"group by status\" — keep the source rows flat and compute markers inside a `group` pipeline stage. The handler returns `{ data, groups }` instead of a bare array, and the pipeline updates rows and markers atomically:\n\n```tsx\nfunction groupBy<T extends Record<string, unknown>>(rows: T[], field: keyof T | null) {\n  if (!field) return { data: rows, groups: [] as { index: number; level: number }[] }\n\n  const buckets = new Map<string, T[]>()\n  for (const row of rows) {\n    const key = String(row[field])\n    let bucket = buckets.get(key)\n    if (!bucket) buckets.set(key, (bucket = []))\n    bucket.push(row)\n  }\n\n  const data: (T | { label: string })[] = []\n  const groups: { index: number; level: number }[] = []\n  for (const [key, bucketRows] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${String(field)}: ${key} (${bucketRows.length})` })\n    data.push(...bucketRows)\n  }\n  return { data, groups }\n}\n\nconst [model] = useState(() =>\n  localModel({\n    data: products,\n    pipeline: ['filter', 'group'],\n    actions: {\n      filter: {\n        stage: 'filter',\n        handler: ({ data, payload }) => (payload ? (data as Product[]).filter((row) => row.category === payload) : data),\n      },\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupBy(data as Product[], payload as keyof Product | null),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'group', payload: 'category' })\n```\n\nStage order matters here. With `['filter', 'group']`, the filter narrows the source rows and the group stage buckets only what survived — the natural pairing. Putting `group` before `filter` would force the filter handler to special-case header rows, which is rarely worth the complexity.\n\nA handler that returns `{ data, groups }` updates both. Later stages that return a bare array reuse the most recent `groups` from earlier in the pipeline, so a `sort` after `group` doesn't need to know about markers as long as it preserves their indexes (typically by sorting within each bucket).\n\nTo push pre-grouped rows from outside the pipeline entirely — for example after a parent fetch — call `model.setData(nextRows, nextGroups)`.\n\n### Connecting the UI\n\n`<GroupHeaderCell>` renders the entries marked as headers. It receives the original row and the marker's `level`, so styling can branch on nesting depth:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  <GroupHeaderCell>\n    {({ row, level }) => (\n      <div\n        style={{\n          paddingLeft: 12 + level * 16,\n          fontWeight: level === 0 ? 700 : 500,\n          background: level === 0 ? '#e8e8e8' : '#f4f4f4',\n        }}\n      >\n        {(row.data as { groupName: string }).groupName}\n      </div>\n    )}\n  </GroupHeaderCell>\n  {/* columns */}\n</DataTable>\n```\n\n`row.data` is whatever object you pushed at that index in `data` — the shape is yours to define, the cell just renders what you return. Group rows are virtualized like every other row, so keep the rendered markup light.\n\nFor the full render-side reference — remote-paginated grouping, sticky-header behavior, and a runnable end-to-end example — see [Grouped Rows](/data-table/grouped-rows/).\n\n## Action persistence\n\nPersist action payloads — search terms, filter values, sort choice, grouping mode — so user intent survives reloads. Row data is never persisted.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: true,\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPass an object instead of `true` to set a custom storage key or define empty-state handling.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: {\n        key: 'categoryFilter',\n        isEmpty: (value) => !value,\n      },\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPair this with `modelStatePersistenceAdapter()` from [State Persistence](/data-table/state-persistence/) to save model action state alongside column width, order, and visibility.\n\n`isEmpty` skips a payload from saved state when it represents \"no active filter\" — empty strings, `\"all\"` selections, `null` values.\n","packages/data-table/docs/2.data-model/02.remote-data-model.md":"---\ntitle: Remote Data Model\ndescription: Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.\nsidebar:\n  label: Remote Data Model\n---\n\nWhen a table's rows live behind an API and there are too many to load up front — paginated catalogs, search results, infinite feeds — `remoteModel()` is the model that handles the back-and-forth with your backend.\n\nYou give it a `fetch` function that loads a slice of rows. The model calls that function at the right moments (the user scrolled into rows that haven't loaded yet, a search changed, an action fired), feeds the results back into the table, and cancels requests that have been overtaken by newer ones.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n    placeholder: loadingProduct,\n  })\n)\n```\n\n## Where to keep the model\n\nThe model carries live state across renders — requests still in flight, the controllers that can cancel them, persisted action payloads, and the bookkeeping that decides what happens when actions overlap. Recreate the model on every render and that state is thrown away: pending fetches get aborted mid-flight and the next render starts the cycle over with fresh ones. The instance has to outlive renders.\n\nFor most remote tables, **`useState` with lazy initialization** is the right place:\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel<Product, Query>({\n    fetch: fetchProducts,\n    initialParams: { category, search },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n  })\n)\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Use this whenever initial params come from URL state, props, or anything else the surrounding component has access to but a module does not.\n\n**Module scope** works for genuinely static config — a fixed endpoint, no per-route params, one table per page:\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { category: 'all', search: '' },\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nBe aware that every mount of every consumer shares the same instance, and therefore the same fetch lifecycle and persisted action state. That can be a feature (cached results survive navigation) or a footgun (two routes appearing to share filter state).\n\nAvoid `useMemo(() => remoteModel(...), [])`. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which for a remote model means cancelling pending fetches and resetting concurrency state silently. `useState` is the only hook with a retention guarantee.\n\n> The remaining examples on this page write `const model = remoteModel({...})` for brevity, focusing each snippet on the option it's introducing. In real code, wrap the call in `useState(() => ...)` or place it at module scope using the rules above.\n\n## Pagination and infinite scrolling\n\n`remoteModel` ships two built-in patterns for deciding what to fetch as the user scrolls. The right choice depends on how your backend serves data and what kind of scrolling experience the table should deliver.\n\n### Pagination with a known total\n\nReach for **offset mode** when the backend accepts `(offset, limit)` slices and can tell you how many rows exist in total. The table renders placeholder rows for everything that hasn't been fetched, so the scrollbar reflects the full dataset, the user can jump anywhere via the scrollbar or keyboard, and each slice is requested as it enters the viewport.\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nThis is what most search-and-filter UIs want: product catalogs, admin tables, anything backed by a SQL `LIMIT/OFFSET` or an Elasticsearch `from/size` query.\n\n### Infinite scrolling\n\nReach for **append mode** when the backend is cursor-based, the total isn't known up front, or new rows arrive over time — social feeds, message timelines, activity logs.\n\n```typescript\nconst model = remoteModel({\n  mode: 'append',\n  fetch: fetchFeed,\n  onViewportChange: defaultAppendViewportHandler,\n  pageSize: 30,\n})\n```\n\nRows accumulate as the user approaches the end of what's already loaded. The scrollbar only covers the loaded portion because the table can't know how many rows are out there; the loading state shows a footer at the bottom while the next page is fetched.\n\n### Custom fetch windows\n\nWhen neither default matches — the backend uses prefetch hints, variable page sizes, or needs requests coalesced into fewer round-trips — write a custom `onViewportChange`. Given the rendered range and the page size, return one or more slices for the model to fetch:\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: ({ endIndex, pageSize, totalCount }) => {\n    const nextOffset = Math.floor((endIndex + 1) / pageSize) * pageSize\n\n    if (nextOffset < totalCount) {\n      return {\n        fetch: [{ offset: nextOffset, limit: Math.min(pageSize, totalCount - nextOffset) }],\n      }\n    }\n  },\n  pageSize: 50,\n})\n```\n\nOffset-mode handlers return `{ fetch: [{ offset, limit }, ...] }`; append-mode handlers return `{ loadMore: true }`.\n\n## Fetch results\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&search=${params.search}`, {\n    signal,\n  })\n\n  return response.json()\n}\n```\n\nOffset mode expects `{ rows, totalCount }`. Add `groups` when the backend returns a flattened grouped result:\n\n```typescript\nreturn {\n  rows: result.rows,\n  groups: result.groups,\n  totalCount: result.totalCount,\n}\n```\n\nFor backends that define the row schema, return schema metadata next to the rows or keep a separate field list in application state — see [Generating Columns at Runtime](/data-table/columns/runtime-columns/).\n\n`pageSize` controls the slice size. Big enough that ordinary scrolling doesn't request on every small move, small enough that refreshes stay responsive.\n\n`placeholder` is the row shape used while first-page rows are still loading. Include every field your column renderers read.\n\n## Remote actions\n\nRemote actions translate UI intent into the next request's params. URL query parameters are the common case; the same pattern works for cursor filters, request bodies, search objects, or anything else `fetch` reads from `params`.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    actions: {\n      search: {\n        strategy: 'supersede',\n        persistence: true,\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      category: {\n        persistence: { key: 'categoryFilter' },\n        handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'search', payload: 'desk' })\n```\n\nReturning new params triggers a refresh. With `persistence: true`, the payload of each action — the search string, the selected category — is saved alongside the rest of the table's persisted state and replayed through the handler the next time the table loads, so users come back to the same filtered view. Pass `capture` and `restore` instead of `true` when you need a custom serialized shape.\n\nFor stateful controls such as sort, filter, group, or search, prefer the model action state as the source of truth. Seed default active controls with `initialActions`, dispatch changes through `model.send()` or `dispatchModelAction$`, and read `modelActionState$` to paint active buttons. Do not mirror sort/filter payloads in React state just to keep controls highlighted; if a control must be controlled by React, explicitly restore that local state when using `modelStatePersistenceAdapter()`.\n\nFor a complete column-header sort control that sends `{ field, direction }` to a remote model, see [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\n## Concurrency and cancellation\n\n`strategy` controls what happens when an action fires while a fetch is still in flight.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    actions: {\n      search: {\n        strategy: 'supersede',\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      exportRows: {\n        strategy: 'queue',\n        handler: ({ params, payload }) => ({ ...params, exportId: String(payload) }),\n      },\n    },\n  })\n)\n```\n\n- `supersede` — cancels the active request for the same action and uses the latest payload. Right for search boxes and filters.\n- `queue` — runs every request in order. Right for ordered workflows where no request can be dropped.\n- `deduplicate` — collapses identical concurrent requests. Right for retry buttons and repeated load commands.\n\nEvery `fetch` call receives an `AbortSignal`. Pass it to `fetch()` or your client library so superseded refreshes, viewport requests, and disconnects cancel cleanly.\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&q=${params.search}`, {\n    signal,\n  })\n  return response.json()\n}\n```\n\n`onError` fires for failed fetches. Aborted requests publish a loading cancellation event instead.\n\n## Loading state\n\nWhile a fetch is in flight the model emits loading events that the table turns into visible UI — a placeholder for the first load, an overlay for refreshes, and a footer for append-mode pagination. The shadcn wrapper renders sensible defaults for all three slots, so most tables don't need to handle loading state by hand. Reach for the events directly only when something outside the table — a global progress bar, a status badge, a logging hook — needs to react to the same lifecycle.\n\nThree phases drive the visible slots:\n\n- `initial` — first unresolved dataset; rendered as `LoadingPlaceholder`.\n- `refresh` — request params changed while old rows stay visible; rendered as `LoadingOverlay`.\n- `end` — append mode is fetching more rows below the current data; rendered as `LoadingFooter`.\n\nOffset viewport fetches show up as placeholder rows rather than table-level loading and don't update `loadingState$`. Manual `loadRange` calls publish `viewport` events for subscribers, but the loading slots ignore them.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nFor external controls, logging, or diagnostics, subscribe to the model's event channel:\n\n```typescript\nconst unsubscribe = model.subscribe((message) => {\n  if (message.type === 'event' && message.payload?.kind === 'loading') {\n    console.log(message.payload.reason, message.payload.phase)\n  }\n})\n```\n\n`loadingState$` (read via `useVirtuosoLoadingState()` or directly inside the table) is updated from the same `initial`, `refresh`, and `end` events.\n","packages/data-table/docs/2.data-model/03.row-keys.md":"---\ntitle: Row Keys\ndescription: Give the table stable row keys so React reconciles rows correctly when the model reorders them.\nsidebar:\n  label: Row Keys\n---\n\n`computeRowKey` returns the React key for a row — the value React uses to decide whether the row in this render is the same logical row as one from the previous render. Provide a stable key (typically derived from `data.id`), and React reconciles the row in place even when its index changes under a sort, filter, group, prepend, or append.\n\n```tsx\n<DataTable computeRowKey={({ data }) => data.id} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## Why row keys matter\n\nWithout `computeRowKey`, the key falls back to the virtual row position. That works for tables whose rows never move, but as soon as the model reorders — a pipeline `filter` stage runs, a `sort` stage reshuffles the result, a new page is appended, a remote refetch returns rows in a new order — every row whose index changed remounts.\n\nThe symptom is usually a user-visible glitch: a search input loses focus mid-keystroke because the row it lives in moved one position, an expanded detail row collapses because its parent shifted, a chart inside a cell redraws because the cell got a new React identity. The data is still correct; the React identity broke. `computeRowKey` is what stops that from happening.\n\nThis is a React reconciliation rule, not a table-specific concern. It applies to any data table where rows can move, regardless of which other table features you use.\n\n## Grouped data\n\nGrouped data mixes data rows and group rows in the same flat array (see [Grouping](/data-table/data-model/local-data-model/#grouping)). The function receives both shapes, so return a key for each:\n\n```tsx\n<DataTable computeRowKey={({ data, index }) => ('id' in data ? data.id : `group:${data.label}:${index}`)} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nGroup rows rarely carry a stable ID from your data, so a composite key built from a label plus index is fine — the table doesn't try to reconcile group rows across regrouping anyway. The important thing is that data rows keep their identity through reorder; group rows are allowed to remount when the grouping dimension changes.\n\n## Function signature\n\n`computeRowKey` receives an object with three fields:\n\n- `data` — the row at this index (the data row or the group row)\n- `index` — the row's virtual position in the table\n- `context` — the table's [ambient context](/data-table/customization/ambient-context/), useful for scoping keys across tenants, saved views, or other instances that might share row IDs\n\n```tsx\n<DataTable computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`} context={{ tenantId: 'acme' }} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nCross-scope key prefixing matters when one component instance is reused for different tenants or views — without it, React would see the same key for what's actually a different row and skip reconciliation it shouldn't have skipped.\n","packages/data-table/docs/2.data-model/index.md":"---\ntitle: Choosing a Data Model\ndescription: Give the table a local or remote model before adding table features.\nsidebar:\n  label: Choosing a Model\n---\n\nThe model owns the rows; columns own the rendering. Pick the model that matches where rows come from:\n\n- [`localModel()`](/data-table/data-model/local-data-model/) — rows already in the browser, with filter, sort, group, and edit actions deriving the displayed rows.\n- [`remoteModel()`](/data-table/data-model/remote-data-model/) — rows fetched from an API as request params, cursors, or the rendered range change.\n\nBoth models can persist action state (filter values, sort choice, grouping mode) through opt-in adapters. Row data itself is never persisted.\n\n## Basic local model\n\nUse a local model for in-memory rows. Keep the model stable and pass it to `DataTable` with\n`model={model}`.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Where to go next\n\n- [Local Data Model](/data-table/data-model/local-data-model/) — pipelines, source updates, persistence, grouping (single- and multi-level).\n- [Remote Data Model](/data-table/data-model/remote-data-model/) — pagination vs. infinite scrolling, actions, cancellation, grouped rows, loading UI.\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","packages/data-table/docs/3.columns/01.defining-columns.md":"---\ntitle: Defining Columns\ndescription: Declare the fields, order, defaults, and basic behavior of a data-table column.\nsidebar:\n  label: Defining Columns\n---\n\n`DataTableColumn` declares a column. For data-backed columns, pass `field`; it reads `row.data[field]`, uses that value as `cellValue`, and uses the field name as the stable column identity for persistence, reordering, resizing, and visibility.\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>Name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor a row `{ name: 'Standing Desk' }`, the cell renderer receives `'Standing Desk'` as `cellValue`. Omitting `DataTableCell` falls back to the field value; omitting `DataTableColumnHeader` on a field-backed column falls back to the field name. In app UIs, prefer explicit headers so you control capitalization, wording, and accessibility.\n\nDisplay-only columns do not need matching row fields. Give them an explicit `id` instead. The id becomes the stable column identity, and `cellValue` is `undefined` unless you provide an `accessor`.\n\n```tsx\n<DataTableColumn id=\"actions\" sticky=\"right\">\n  <DataTableColumnHeader>Actions</DataTableColumnHeader>\n  <DataTableCell>{({ row }) => <ActionsMenu row={row.data} />}</DataTableCell>\n</DataTableColumn>\n```\n\n`id` is not a visible label. For action, selection, expansion, status-icon, and other display-only columns, always provide a `DataTableColumnHeader`. Use visible text such as `Actions` by default; use `sr-only` text only when the product intentionally wants a headerless icon column.\n\nComputed columns also use an explicit `id`, plus an `accessor` when you want the table to provide a derived `cellValue`.\n\n```tsx\n<DataTableColumn id=\"fullName\" accessor={(row) => `${row.firstName} ${row.lastName}`}>\n  <DataTableColumnHeader>Full name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Minimal columns\n\nAdd an explicit header or cell when you need formatting; leave both off when the field name and stringified value are enough.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 260 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\" />\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Declaration order\n\nColumns render in declaration order. Reordering and persistence change the runtime order; the declaration order stays as the baseline used after a reset.\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"name\" />\n  <DataTableColumn field=\"category\" />\n  <DataTableColumn field=\"price\" />\n</DataTable>\n```\n\n## Visibility and sticky\n\n`visible={false}` hides a column by default. Runtime visibility lives in the [Column Visibility](/data-table/columns/column-visibility/) page.\n\n```tsx\n<DataTableColumn field=\"internalSku\" visible={false} />\n```\n\n`sticky=\"left\"` or `sticky=\"right\"` pins a column for row identity, IDs, or actions that should stay visible during horizontal scrolling — see [Sticky Columns](/data-table/columns/sticky-columns/).\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\" />\n<DataTableColumn id=\"actions\" sticky=\"right\" />\n```\n\n## Generated columns\n\nFor dynamic field lists, map them to `DataTableColumn` declarations. Keep the field order deterministic — the field values become the column identities.\n\n```tsx\nconst fields = ['name', 'category', 'stock']\n\n;<DataTable model={model}>\n  {fields.map((field) => (\n    <DataTableColumn field={field} key={field}>\n      <DataTableColumnHeader>{field}</DataTableColumnHeader>\n      <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n    </DataTableColumn>\n  ))}\n</DataTable>\n```\n\nWhen the field list itself is discovered from the first data result, see [Runtime Columns](/data-table/columns/runtime-columns/).\n","packages/data-table/docs/3.columns/02.cell-and-header-renderers.md":"---\ntitle: Formatting Cells and Headers\ndescription: Use render params to format cells and headers from column and row state.\nsidebar:\n  label: Formatting Cells\n---\n\nMost columns need formatting that goes beyond stringifying a field — currency values, status badges, stock-level colors, multi-line product names. `DataTableCell` and `DataTableColumnHeader` are where that formatting lives. Each one accepts a render function that the table calls when it paints a body cell or header; only cells inside (or close to) the viewport actually run, so the JSX stays a static, declarative description of the structure while the work scales to large tables.\n\n## Style the correct header surface\n\n`DataTableCell` accepts a `className` prop alongside its render function. The class lands on the table's own cell wrapper, so use it for alignment, font weight, text color, `tabular-nums`, and sticky-specific colors instead of wrapping the render output in another `<div>`.\n\n```tsx\n<DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n```\n\nHeaders have two surfaces because a grow column has both a base width and a rendered width. The table observes the intrinsic header to determine the base width, then assigns spare space to growing columns. If the observed element also stretches across that spare space, its rendered width becomes the next base measurement and the column cannot shrink when the table narrows. Keeping measurement and presentation on separate elements prevents that feedback loop.\n\n`className` styles the intrinsic header content that the table measures. Use it for width, minimum width, padding, typography, and alignment within the base width. `containerProps` styles the outer container after the table assigns the rendered column width. Use it for paint and interaction that must cover the full column, such as a background, hover state, outline, box shadow, cursor, ARIA attribute, or event handler.\n\n```tsx\n<DataTableColumnHeader\n  className=\"min-w-64\"\n  containerProps={{\n    className: 'cursor-pointer hover:bg-muted/50',\n  }}\n>\n  Product\n</DataTableColumnHeader>\n```\n\nThe outer container's geometry belongs to the table. Do not put width, flex, margin, padding, border, display, or `justify-*` utilities on `containerProps`; these can clip the measured content or move edge-aligned header slots. Keep those layout styles on `className`, use `box-shadow` or `outline` for full-width separators, and use `HeaderEnd` for a control that must stay at the rendered column edge.\n\nEvents attached through `containerProps` receive bubbled events from header slots and nested controls. When a header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nKeep width utilities such as `w-*` and `min-w-*` on `DataTableColumnHeader.className`. A `w-full` or flex-growth class on this intrinsic surface resolves only within the measurement boundary; it does not cover the rendered track. Use `containerProps` for that visual result. Do not put width utilities on `DataTableCell`. Use `DataTableColumn grow={...}` when a column should absorb leftover horizontal space. A cell width can make the body content fight the measured grid track and overlap neighboring columns. For cell content that needs truncation or nested layout, keep the cell unconstrained and put `min-w-0` on an inner wrapper:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-64\">Name</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ row }) => <div className=\"min-w-0 truncate\">{row.data.name}</div>}</DataTableCell>\n</DataTableColumn>\n```\n\nEach visible cell is its own element; the table also column- and row-virtualizes, so an extra wrapper inside the render function multiplies by every rendered cell on every scroll. Save nested elements for styling that's only part of the cell content — a status badge, an inline icon, a secondary line of metadata.\n\nThe shadcn wrapper accepts plain text for static headers:\n\n```tsx\n<DataTableColumnHeader>Product</DataTableColumnHeader>\n```\n\nAnd a render function for headers that depend on table state:\n\n```tsx\n<DataTableColumnHeader>{({ columnState }) => (columnState.sticky ? 'Pinned product' : 'Product')}</DataTableColumnHeader>\n```\n\n## Cell values and rows\n\n`cellValue` is the selected field value or the result of the column `accessor`; `row.data` is the whole row. Reach for `row.data` whenever the cell depends on more than one field or renders UI that is not data-backed.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : `${row.data.stock} in stock`}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Render params\n\nThe render function receives a single object whose fields surface what you'd otherwise have to look up by hand. `cellValue` is the shortcut for `row.data[column.field]` on field columns, the result of `accessor(row.data)` on computed columns, and `undefined` on id-only display columns. `row.data` is there for cells that depend on more than one field. `column` and `columnKey` identify which column this invocation is rendering — useful when one extracted renderer drives multiple columns. `column.id` is the stable public identity when you provided one; `column.field` exists only for columns that read a row-data field. `columnState` lets the renderer branch on runtime column state (currently just `sticky`, so headers and cells can style differently when the column is pinned). `overlaidByScrollbar` flips to `true` when a native OS scrollbar overlaps the cell's edge — that's the table telling you trailing UI in this cell is unreachable, so move buttons or actions inboard while the flag is set. Header params have the same shape minus `cellValue` and `row` — a header has no row to render against.\n\n```typescript\ntype CellRenderParams = {\n  cellValue: unknown // row.data[column.field], accessor(row.data), or undefined\n  row: RowItem // the virtualized row\n  column: ColumnDefinition\n  columnKey: string // internal key for this column instance\n  columnState: ColumnState // currently: { sticky }\n  overlaidByScrollbar: boolean // true when a native scrollbar overlaps the cell edge\n}\n\ntype ColumnHeaderRenderParams = Omit<CellRenderParams, 'cellValue' | 'row'>\n```\n\n```tsx\n<DataTableColumnHeader>\n  {({ column, columnState }) => <span className={columnState.sticky ? 'font-semibold' : undefined}>{column.field}</span>}\n</DataTableColumnHeader>\n```\n\n## Extracting renderers\n\nInline render functions become unwieldy past a couple of lines, and they limit what you can do with the renderer itself. Pulling them out into named functions — or full components — buys three things at once: they appear by name in React DevTools (instead of as anonymous arrow functions), one renderer can drive several columns that share the same formatting, and you can wrap the renderer in `React.memo` so it skips re-renders when the row data hasn't changed — useful for expensive cells like rich text or chart sparklines.\n\nThe render-param types are exported and double as the props type for the component:\n\n```tsx\nimport type { CellRenderParams, ColumnHeaderRenderParams } from '@virtuoso.dev/data-table'\n\nfunction StockCell({ row }: CellRenderParams) {\n  return <span>{row.data.stock < 20 ? 'Low stock' : 'Healthy'}</span>\n}\n\nfunction FieldHeader({ column }: ColumnHeaderRenderParams) {\n  return <span>{column.field ?? column.id}</span>\n}\n```\n\nExtracted renderers are ordinary React components. They can call hooks, subscribe to React context, and compose with `React.memo` like any other component. One thing they _don't_ receive is the table's `context` prop — that channel exists for table infrastructure (empty-state, sticky wrappers, scroll element) and doesn't reach cell or header renderers. Close over screen-local state directly, or read it through React context if multiple columns need it.\n\nPass renderers as children of the cell or header:\n\n```tsx\n<DataTableColumn field=\"stock\">\n  <DataTableColumnHeader>{FieldHeader}</DataTableColumnHeader>\n  <DataTableCell>{StockCell}</DataTableCell>\n</DataTableColumn>\n```\n\n## Header slots\n\nA header can host additional UI alongside the label — sort buttons, filter menus, resize handles, drop indicators for reordering. Inline UI (buttons, menus) can go in the header's render function. Two positions don't fit there: a resize handle has to sit on the column boundary independent of label alignment, and a drop indicator has to span the entire header during a reorder drag. The table provides four named slots for these cases:\n\n- `HeaderStart` — before the label, in the header's flex layout. Drag grips, leading icons.\n- `HeaderEnd` — after the label, in the same flex layout. Sort buttons, menu triggers.\n- `HeaderEdge` — pinned to the column boundary, outside label layout. Resize handles.\n- `HeaderOverlay` — spans the whole header. Drop indicators, focus rings.\n\nDeclare slots inside the header; the table positions each one according to its slot type. Slot children receive the same render params as the header, so a sort button can read `column.field` to know which column it's acting on.\n\n```tsx\n<DataTableColumnHeader>\n  {() => 'Price'}\n  <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n</DataTableColumnHeader>\n```\n\nWhen a header has any slots, the label must be a render function — plain string children are only valid for headers without slots. That's why the example above writes `{() => 'Price'}` instead of just `Price`.\n\nThe shadcn wrapper's resize and reorder UI is built on these slots. See [Header Slots](/data-table/customization/header-slots/) for the placement diagram, the `component` prop alternative to render-function children, and complete recipes for sort, filter, and menu slots.\n","packages/data-table/docs/3.columns/03.column-groups.md":"---\ntitle: Column Groups\ndescription: Organize related columns under shared headers.\nsidebar:\n  label: Column Groups\n---\n\n`ColumnGroup` wraps adjacent columns and renders one shared header above them — \"Inventory\" sitting above stock and status, \"Fulfillment\" above region and category. In a wide table, the grouped header gives the reader a second-tier band to scan instead of counting columns from the left edge to locate a field. Use it when neighbouring columns belong together as a named bundle (inventory metrics, fulfillment details, customer attributes) and the table is wide enough that the bundle isn't obvious from column order alone.\n\n```tsx live wide\nimport { localModel } from '@virtuoso.dev/data-table'\nimport {\n  ColumnGroup,\n  ColumnGroupHeader,\n  DataTable,\n  DataTableCell,\n  DataTableColumn,\n  DataTableColumnHeader,\n} from '@/components/ui/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', category: 'Office', stock: 14, status: 'Ready', region: 'US' },\n  { name: 'USB-C Dock', category: 'Device', stock: 42, status: 'Ready', region: 'EU' },\n  { name: 'Keyboard', category: 'Device', stock: 8, status: 'Low', region: 'APAC' },\n]\n\nconst model = localModel({ data: rows })\nconst groupHeaderClassName = 'flex h-8 items-center justify-center bg-muted/40 px-2 text-xs font-medium text-muted-foreground'\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 300 }}>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Catalog'}</ColumnGroupHeader>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Inventory'}</ColumnGroupHeader>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Fulfillment'}</ColumnGroupHeader>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n    </DataTable>\n  )\n}\n```\n\nGrouping changes only the header. Each column inside a group keeps its own field, cell renderer, width, visibility, and saved state — the body grid never notices the grouping exists. That separation is what makes the next sections work: every per-column feature still applies, with a few group-level twists worth knowing.\n\n## Sticky groups\n\nPin a whole group with `<ColumnGroup sticky=\"left\">` (or `\"right\"`). Every column inside follows the group to the edge during horizontal scroll — it's a shorthand for declaring `sticky=\"left\"` on each child, and the group header stays anchored above its columns.\n\n```tsx\n<ColumnGroup sticky=\"left\">\n  <ColumnGroupHeader>{() => 'Identity'}</ColumnGroupHeader>\n  <DataTableColumn field=\"id\">…</DataTableColumn>\n  <DataTableColumn field=\"name\">…</DataTableColumn>\n</ColumnGroup>\n```\n\nOnly top-level groups can be sticky — `sticky` on a nested `<ColumnGroup>` is ignored. Children inherit sticky-ness from the nearest sticky ancestor, so nested groups don't need their own opt-in.\n\n## Resizing inside a group\n\nResize handles work unchanged on columns inside groups. Each column owns its width override; the group header automatically re-sums from its children on every resize, so dragging \"Stock\" wider stretches the \"Inventory\" band to match. You never set a width on the group itself — there's no group-level resize API, because distributing pixels across child columns is rarely what users mean when they grab a header.\n\n## Reordering with groups\n\nTwo reorder gestures coexist:\n\n- **Move a whole group.** `DraggableGroupHeader` (from the shadcn `column-reorder` registry) makes the group header itself draggable. The user drags \"Inventory\" between \"Catalog\" and \"Fulfillment\" and all of Inventory's columns travel together, keeping their internal order.\n- **Move one column.** `ReorderGrip` on individual column headers keeps working — useful for nudging a column within its group.\n\nBoth gestures respect sticky regions: a left-sticky source can only land among other left-sticky targets, and so on. That guard is built into the registry components, so you can drop them in without filtering by hand.\n\nThere's a footgun in mixing the two: **a single-column drag that crosses a group boundary breaks the visual layout.** The header tree pairs each column with its declared `<ColumnGroup>` regardless of where it now sits in column order, so a column dragged out of group A and dropped between group B's columns produces a body row in the new order but a header band that's no longer contiguous. The whole-group drag never has this problem — it moves every key in one transaction.\n\nTwo practical responses:\n\n- Mount `ReorderGrip` only on ungrouped columns (or only inside groups, never both), so users can't initiate a cross-group single-column drag.\n- Or keep both gestures and accept that cross-group moves split the band. Works for tables where the grouping is informational rather than structural.\n\n`reorderColumnGroup$` is the underlying engine action if you wire your own group-drag UI; `DraggableGroupHeader` publishes to it. See [Column Reordering](/data-table/columns/column-reordering/) for the full reorder surface.\n\n## Nesting\n\n`<ColumnGroup>` can wrap other `<ColumnGroup>` declarations for two-tier categorization — \"Customer\" above \"Personal\" and \"Account\", each above their own columns:\n\n```tsx\n<ColumnGroup>\n  <ColumnGroupHeader>{() => 'Customer'}</ColumnGroupHeader>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Personal'}</ColumnGroupHeader>\n    <DataTableColumn field=\"name\">…</DataTableColumn>\n    <DataTableColumn field=\"email\">…</DataTableColumn>\n  </ColumnGroup>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Account'}</ColumnGroupHeader>\n    <DataTableColumn field=\"plan\">…</DataTableColumn>\n  </ColumnGroup>\n</ColumnGroup>\n```\n\nEach nesting level stacks one extra header row. Two levels usually covers what's worth grouping; deeper hierarchies steal vertical space the data needs.\n\n## Layout notes\n\n- Each nesting level adds a full header row. Keep group labels short and the row height modest — `text-xs` with small padding is the usual register.\n- Group headers render full-width over their span. The label sits inside that bar, so very long labels either wrap or get clipped depending on your CSS.\n","packages/data-table/docs/3.columns/04.sticky-columns.md":"---\ntitle: Sticky Columns\ndescription: Pin high-value columns to the left or right side of a wide table.\nsidebar:\n  label: Sticky Columns\n---\n\nPass `sticky=\"left\"` or `sticky=\"right\"` to a `DataTableColumn` and it stays in place while the rest of the row scrolls under it horizontally. Use this to keep row-identifying columns (typically a product name) visible on the left, and status or action columns visible on the right, when the table is wide enough to scroll sideways.\n\nTwo layout constraints have to hold for the effect to work:\n\n- Pinned columns need an explicit minimum width on the header (`min-w-[…]` on `DataTableColumnHeader`). Without one, the column can collapse to its content width and overlap the scrollable cells sliding past it. See [Column Resizing](/data-table/columns/column-resizing/#where-column-widths-live) for why width belongs on the header, not the cell.\n- Every pinned column subtracts from the scrollable area. Pin one or two columns per edge — pinning more shrinks the scroll region until the feature stops earning its keep.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  owner: ['Avery', 'Morgan', 'Riley'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n  supplier: ['Northwind', 'Contoso', 'Fabrikam'][index % 3]!,\n  warehouse: ['Denver', 'Rotterdam', 'Singapore'][index % 3]!,\n  price: 49 + (index % 8) * 24,\n  stock: (index * 7) % 64,\n  leadTime: `${2 + (index % 5)} days`,\n  margin: `${18 + (index % 6) * 3}%`,\n  updated: `2026-05-${String(1 + (index % 17)).padStart(2, '0')}`,\n}))\n\nconst model = localModel({ data: rows })\nconst scrollColumns = [\n  ['category', 'Category'],\n  ['owner', 'Owner'],\n  ['region', 'Region'],\n  ['supplier', 'Supplier'],\n  ['warehouse', 'Warehouse'],\n  ['price', 'Price'],\n  ['stock', 'Stock'],\n  ['leadTime', 'Lead time'],\n  ['margin', 'Margin'],\n  ['updated', 'Updated'],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\" sticky=\"left\">\n        <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      {scrollColumns.map(([field, label]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader className=\"min-w-[140px]\">{label}</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n      <DataTableColumn field=\"status\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe relevant lines, isolated:\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\">\n  <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"status\" sticky=\"right\">\n  <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Changing pinning at runtime\n\n`sticky` on `DataTableColumn` sets the initial pinning. When the user needs to pin or unpin a column from a toolbar — say, a \"Pin to left\" menu item — publish to the table's `setColumnSticky$` channel instead of re-rendering with a new prop. The [Sticky Columns example](/data-table/examples/sticky-columns/) wires up that external toggle end-to-end; [Controlling the Table](/data-table/controlling-the-table/) covers the engine API the channel belongs to.\n","packages/data-table/docs/3.columns/05.column-visibility.md":"---\ntitle: Column Visibility\ndescription: Hide and restore columns with declarative defaults, reactive controls, and persistence.\nsidebar:\n  label: Column Visibility\n---\n\nColumn visibility has two flavours: a declarative default that hides a column at mount, and a runtime channel that flips visibility from a \"Columns\" menu, a saved view, or a similar toggle. The two compose — declare hidden defaults for columns like internal IDs, audit timestamps, and other secondary metadata, then expose a runtime control for users who need to opt back in.\n\n```tsx live wide\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { columns$, localModel, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n]\n\nfunction labelFor(field: string) {\n  return field.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"flex flex-wrap items-center gap-3 rounded-lg border bg-muted/30 px-3 py-2 text-sm\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n\n          return (\n            <label key={key} className=\"flex items-center gap-2\">\n              <input checked={visible} onChange={() => setColumnVisibility({ key, visible: !visible })} type=\"checkbox\" />\n              {labelFor(column.field)}\n            </label>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} size=\"sm\" variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 280 }}>\n        <DataTableColumn field=\"product\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Hiding at declaration time\n\nPass `visible={false}` to a `DataTableColumn` and it stays out of the rendered grid:\n\n```tsx\n<DataTableColumn field=\"internalId\" visible={false}>\n  <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nThe column is still _registered_ — that's what makes it selectable in a picker. It just doesn't take a slot in the header, body, or scroll measurement. There's no layout cost beyond keeping its JSX in the tree, so this is also the right shape for \"hidden by default, sometimes useful\" columns that don't need a runtime toggle.\n\n## Toggling at runtime\n\nPublish to `setColumnVisibility$` (from `@virtuoso.dev/data-table/column-visibility`) with a runtime column key and the boolean you want the column to end up at:\n\n```tsx\nsetColumnVisibility({ key, visible: !isVisible })\n```\n\nRead the current state from `columnVisibilityState$` — a `Map<key, boolean>` with one entry per registered column, declarative defaults already folded in. Consumers don't have to fall back to `columns$` for \"what's the default again?\"; the resolved value is right there.\n\n`resetColumnVisibility$` (a `Trigger`, no payload) clears every override at once — typically wired to a \"Reset to defaults\" button. And there's a quieter convenience worth knowing: when the user toggles a column back to its declarative default, the override is dropped instead of stored as a redundant entry. \"Hide then show\" returns the table to a clean state, and the saved-state side of the feature stays minimal as a result.\n\n## Where the controls live\n\nThe demo above sits beside the table and connects through `engineRef`, using `useRemoteCellValue` and `useRemotePublisher`. That's the common shape — a toolbar, a preset menu, a settings panel — anywhere the controls aren't a child of `<DataTable>`.\n\nFor controls mounted _inside_ the table tree — a header slot, a footer, a context menu rendered from a cell — the simpler `useCellValue` and `usePublisher` hooks from `@virtuoso.dev/reactive-engine-react` work, picking up the same engine through React context:\n\n```tsx\nimport { columns$ } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\nimport { useCellValue, usePublisher } from '@virtuoso.dev/reactive-engine-react'\n\nfunction ColumnVisibilityPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div>\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key)\n        return (\n          <button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} type=\"button\">\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n```\n\n[Controlling the Table](/data-table/controlling-the-table/) covers the in-tree-vs-sibling distinction in depth.\n\n## How visibility composes with other column features\n\n- **Groups.** Hiding a column shrinks its `ColumnGroup` header band to whatever children remain visible. Hide every column in a group and the group header disappears with them — there's no empty band.\n- **Sticky.** A hidden sticky column simply vanishes; the columns next to it stay where they are rather than sliding into the pinned region.\n- **Reorder.** Visibility and order are independent — hiding a column doesn't change its position in `columns$`, and showing it again restores it to the slot it held before.\n\n## Saving the user's choice\n\n`columnVisibilityPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to save visibility across reloads. The saved shape is keyed by stable field name and contains only the _differences from declarative defaults_, so:\n\n- A user who hasn't touched the picker saves nothing.\n- A column that started visible and stays visible never appears in saved state.\n- Hiding a default-visible column writes one entry; un-hiding it drops the entry back out.\n\nThat makes the saved bundle forward-compatible: adding a new column to the JSX with a sensible declarative default doesn't surprise existing users with a stale override on next load. The catch sits on the other side — renaming a `field` invalidates the saved visibility for that field, since the saved state can no longer find the column it was about. The [field-name persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\nSee the [Column Visibility example](/data-table/examples/column-visibility/) for a runnable sibling-toolbar setup.\n","packages/data-table/docs/3.columns/06.column-resizing.md":"---\ntitle: Column Resizing\ndescription: Let users change column widths with the opt-in resize model and shadcn resize handle.\nsidebar:\n  label: Column Resizing\n---\n\nA column's default width comes from its content and the table's layout — usually fine, sometimes not. Long product names need more room than the default; an ID column can shrink. Column resizing gives users a drag handle on the header edge to set the width themselves, and the table remembers their choice once persistence is wired up.\n\n## Adding the drag handle\n\nMount `ResizeHandle` from the shadcn `data-table-resize-handle` registry item inside a `HeaderEdge` slot. The handle owns the pointer interaction, the live preview while dragging, and the publish of the final width back to the table — the column declaration only has to expose the slot.\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-resize`.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 60 }, (_, index) => ({\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe slot alone is what enables resizing on a column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderEdge component={ResizeHandle} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nSkip `HeaderEdge` for columns the user shouldn't be allowed to resize — a fixed-width row-selector column, for example.\n\n## Where column widths live\n\nThe table owns every column's width through its header. It measures each `DataTableColumnHeader`, distributes any leftover viewport space across the visible columns, and uses the resulting size for every body cell in that column. The drag handle, the programmatic `resizeColumn$` call, and the persisted overrides feed into that same pipeline as user-selected rendered widths. While a resize override is active, sibling columns keep their current rendered widths so dragging a boundary feels direct and can create horizontal overflow.\n\nTo set a column's _base_ width, style the header with width or minimum-width classes. To decide which columns receive leftover horizontal space, use `DataTableColumn grow={number}`. Do not use Tailwind flex growth classes as the table sizing API:\n\n```tsx\n<DataTableColumn field=\"id\">\n  <DataTableColumnHeader className=\"w-20\">ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-48\">Product</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor the full sizing model, including fixed metadata columns, text-heavy grow columns, horizontal overflow, and resize override behavior, see [Column Layout](/data-table/columns/column-layout/).\n\n:::caution[Don't put widths on the cell]\nBody cells render inside a grid track sized to the column's measured width — they're already constrained from the outside. Width CSS on `DataTableCell` (`w-*`, `min-w-*`, `max-w-*`, `flex-*`, `basis-*`) doesn't influence the column and can fight the grid into clipped or overflowing content. When a column needs to be wider, widen its header. Reserve cell CSS for things inside the track: padding, alignment, typography, truncation.\n:::\n\nThis includes width pressure added for \"realistic\" content. If a prompt name, badge stack, action menu, or timestamp needs room, express that as a header width:\n\n```tsx\n<DataTableColumn field=\"updated_at\">\n  <DataTableColumnHeader className=\"min-w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror that width on the body cell. For multi-line or nested content, place layout helpers inside the cell renderer:\n\n```tsx\n<DataTableCell>\n  {({ row }) => (\n    <div className=\"min-w-0\">\n      <p className=\"truncate\">{row.data.name}</p>\n      <p className=\"truncate text-muted-foreground\">{row.data.slug}</p>\n    </div>\n  )}\n</DataTableCell>\n```\n\nAs a final review step, search for width utilities on cell declarations:\n\n```bash\nrg 'DataTableCell.*className=.*(w-|min-w|max-w|basis-|grow|shrink|flex-(none|auto|initial|1|\\[))'\n```\n\n## Resizing without the drag handle\n\nSome resizes don't originate from the column edge. A \"fit to content\" toolbar button, a \"Compact / Comfortable / Spacious\" preset, a keyboard shortcut — each needs to set the width without anyone dragging. Publish to `resizeColumn$` directly:\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n\nfunction fitNameColumn() {\n  const nameKey = [...(columns ?? new Map())].find(([, column]) => column.field === 'name')?.[0]\n  if (nameKey) {\n    resizeColumn({ key: nameKey, width: 280 })\n  }\n}\n```\n\nThe payload's `key` is the column's internal identifier, not its `field`. The lookup against `columns$` translates a stable, user-facing field name into the key the stream expects.\n\n## Persisting widths across reloads\n\nThe handle records the user's _intent_ — \"the user wants the name column 280px wide.\" Intent is what gets saved. Automatic `grow` output is not persisted; clearing the override lets the column fall back to its measured base width and the current grow distribution.\n\nMount `columnWidthPersistenceAdapter()` on `DataTableStatePersistence` to persist overrides. The adapter saves them keyed by `field`, so the saved entry survives reordering, partial column lists, and dynamically generated columns. [State Persistence](/data-table/state-persistence/) covers the full wiring, including the field-name contract that ties saved state to column declarations.\n\n## The full resize surface\n\nThree publishers cover the runtime mutations:\n\n- `resizeColumn$` — set a width override for one column.\n- `clearColumnWidthOverride$` — drop one override; the column falls back to its default.\n- `resetColumnWidthOverrides$` — drop every override at once, e.g. a \"Reset widths\" menu item.\n\nOne adapter wires those into persistence:\n\n- `columnWidthPersistenceAdapter()` — saves the override map; see [State Persistence](/data-table/state-persistence/).\n\nOne read-only cell exposes the realized layout, for layout-aware UI that needs the actual rendered widths:\n\n- `columnWidths$` — realized widths after distribution. Read for layout introspection; do **not** persist these values.\n\nThe [Column Resizing example](/data-table/examples/column-resizing/) puts the handle, preset menus, and persistence together in one table.\n","packages/data-table/docs/3.columns/07.column-reordering.md":"---\ntitle: Column Reordering\ndescription: Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.\nsidebar:\n  label: Column Reordering\n---\n\nColumn reordering lets the user move columns into a different position than the JSX declares. The order in JSX becomes the initial order; the user changes it through a drag UI mounted in the header, a toolbar action, or programmatic engine calls.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 320 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n## Wiring the handles\n\nTwo pieces mount inside each column header: a draggable grip the user can grab, and a drop zone that paints the indicator line when another column is dragged over. They live in two different header slots so they don't fight each other for space — the grip sits in `HeaderStart` next to the title, and the drop indicator overlays the whole header through `HeaderOverlay` without disturbing layout.\n\n```tsx\nimport { HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\n;<DataTableColumn field=\"status\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Status'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n`ReorderGrip` and `ReorderDropZone` are shadcn registry components — drop them into a column header and reordering works. They're not the whole story, though: under them is an engine action any other surface can publish to.\n\n## Reordering from a toolbar or menu\n\nDrag isn't the only way to move columns. A \"Move to first\" menu item or an arrow-key shortcut can reach for `reorderColumns$` from outside the header. The shape is the same as what the drag UI publishes: source, target, and a `before` / `after` position.\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { reorderColumns$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst reorderColumns = useRemotePublisher(reorderColumns$, engineRef)\n\nconst entries = [...(columns ?? new Map())]\nconst statusKey = entries.find(([, column]) => column.field === 'status')?.[0]\nconst nameKey = entries.find(([, column]) => column.field === 'name')?.[0]\n\nif (statusKey && nameKey) {\n  reorderColumns({ sourceKey: statusKey, targetKey: nameKey, position: 'before' })\n}\n```\n\nThe two-step lookup is genuine API friction worth knowing: `reorderColumns$` operates on runtime _column keys_ (the identity the engine uses internally), not the `field` names you write in JSX. Most callers know the field — \"move 'status' before 'name'\" — and translate by walking `columns$` once. Column keys are stable for a given declaration, so for repeated programmatic moves it's worth building the field → key map once at mount and stashing it in a ref.\n\n## Resetting to declaration order\n\nFor a \"Reset to default order\" button, publish `resetColumnOrderToDeclaration$`. The table already tracks the current component declaration order internally, so the caller doesn't need to snapshot runtime column keys at mount.\n\n```tsx\nimport { useEngineRef, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnOrderToDeclaration$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst resetColumnOrder = useRemotePublisher(resetColumnOrderToDeclaration$, engineRef)\n\n;<button onClick={() => resetColumnOrder()} type=\"button\">\n  Reset order\n</button>\n```\n\nIf `columnOrderPersistenceAdapter()` is mounted, the reset is treated like any other order change and the declaration-order state is saved under the table's persistence key.\n\n## Sticky and group constraints\n\nTwo rules narrow what the drag UI accepts, and they apply equally to engine-level reorders:\n\n- **Sticky stays with sticky.** A drag from a `sticky=\"left\"` column only accepts drops on other left-sticky targets; right-sticky behaves the same. Non-sticky columns can't be pulled into a pinned region by the drag UI, and the engine action treats sticky-ness as a column property rather than a positional one — moving a non-sticky column next to a pinned one doesn't make it pinned.\n- **Group drag is its own gesture.** When columns live inside a `ColumnGroup`, the group header itself can be made draggable with `DraggableGroupHeader`, moving every member as one transaction. The single-column gesture stays available, but dragging an individual column across group boundaries breaks the visual band — see [Column Groups → Reordering with groups](/data-table/columns/column-groups/#reordering-with-groups) for the full picture and the practical mitigations.\n\n## Saving the user's order\n\n`columnOrderPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to keep the user's order across reloads. The saved shape is a list of `field` names — stable, declarative identifiers — rather than the runtime keys the engine uses internally. As a result:\n\n- Adding a new column to the JSX places it at its declared position on next load. Saved state doesn't list it, so the new column doesn't get a stale slot somewhere arbitrary.\n- Removing a column drops it from saved state without disturbing the others.\n- Renaming a `field` invalidates that entry's saved position — the saved state can't find the column it was about. [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\n## Building your own drag UI\n\nThe shadcn grip and drop zone are a reference implementation. If your design system uses a different drag library, you want long-press for touch, or the drop indicator needs custom positioning, the underlying coordination primitives are public:\n\n- `columnDragState$` — a reactive cell holding \"what's being dragged, where's the drop indicator.\" Grip writes, drop zone reads.\n- `beginColumnDrag$` / `endColumnDrag$` — lifecycle channels published from the grip's `dragstart` / `dragend`.\n- `setColumnDropTarget$` — what the drop zone publishes from `dragover`.\n\nThe source for `ReorderGrip` and `ReorderDropZone` in the shadcn registry is short and self-contained; copy it as a starting point and swap out the markup that doesn't fit.\n\nSee the [Column Reordering example](/data-table/examples/column-reorder/) for a runnable setup with persistence wired in.\n","packages/data-table/docs/3.columns/08.runtime-columns.md":"---\ntitle: Generating Columns at Runtime\ndescription: Discover and declare table columns when the field list is only known after data arrives.\nsidebar:\n  label: Runtime Columns\n---\n\nSome tables cannot name every column at build time. An admin viewer may render tenant-defined fields, a debug tool may inspect arbitrary JSON, or a report may receive its visible fields from the first data result.\n\n`DynamicColumns` is for that case. It waits for the first non-empty data result, calls your render function with that data, and lets you return normal `DataTableColumn` declarations. The columns stay fixed for the lifetime of that table instance — if the dataset shape changes later, remount the table with a new `key`.\n\n## What the render function receives\n\nThe render function is called with `{ data, model }`:\n\n- `data` is the first non-empty array the table captured. It does not change when the model later fetches more rows, filters, or fills in remote placeholders — you scan it once and produce columns from what's there.\n- `model` is the same handle you passed to `DataTable`, available for the rare case where a generated header or cell needs to publish a model action.\n\nField scanning lives in your app code so the rules — which fields to include, what counts as a sample value, how to handle group rows — stay obvious next to the table. A general-purpose starting point looks like this:\n\n```ts\ntype RuntimeField = {\n  field: string\n  sampleValue: unknown\n}\n\nfunction fieldsFromRows<Row>(rows: readonly Row[], includeField: (field: string) => boolean = () => true): RuntimeField[] {\n  const fields: RuntimeField[] = []\n  const fieldByName = new Map<string, RuntimeField>()\n  const hasSample = new Set<string>()\n\n  for (const row of rows) {\n    if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n      continue\n    }\n\n    for (const [field, value] of Object.entries(row)) {\n      if (!includeField(field)) {\n        continue\n      }\n\n      let descriptor = fieldByName.get(field)\n      if (!descriptor) {\n        descriptor = { field, sampleValue: value ?? null }\n        fieldByName.set(field, descriptor)\n        fields.push(descriptor)\n      }\n\n      if (!hasSample.has(field) && value !== null && value !== undefined) {\n        descriptor.sampleValue = value\n        hasSample.add(field)\n      }\n    }\n  }\n\n  return fields\n}\n```\n\nThat helper preserves first-seen field order and returns `{ field, sampleValue }` for each field. `sampleValue` is the first non-nullish value seen for that field, or `null` if the field exists but only has nullish values in the captured data.\n\n## Local data\n\nStatic columns declared around `DynamicColumns` keep their positions even though the runtime columns are produced between them. This example pins `id` on the left and `actions` on the right, and lets the table fill in everything else from the first local result.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { DynamicColumns, localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, region: 'US' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, region: 'EU' },\n]\n\ntype Product = (typeof rows)[number]\n\nconst model = localModel<Product>({ data: rows })\n\nfunction labelFor(field: string) {\n  return field.replaceAll('_', ' ').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nfunction fieldsToShow(rows: readonly Product[]) {\n  const fields: string[] = []\n  const seen = new Set<string>()\n\n  for (const row of rows) {\n    for (const field of Object.keys(row)) {\n      if (field !== 'id' && !seen.has(field)) {\n        seen.add(field)\n        fields.push(field)\n      }\n    }\n  }\n\n  return fields\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 300 }}>\n      <DataTableColumn field=\"id\" sticky=\"left\">\n        <DataTableColumnHeader className=\"w-24\">SKU</DataTableColumnHeader>\n        <DataTableCell className=\"text-muted-foreground tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      <DynamicColumns<Product>>\n        {({ data }) =>\n          fieldsToShow(data).map((field) => (\n            <DataTableColumn field={field} key={field}>\n              <DataTableColumnHeader className=\"min-w-32\">{labelFor(field)}</DataTableColumnHeader>\n              <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n            </DataTableColumn>\n          ))\n        }\n      </DynamicColumns>\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"w-24\">Actions</DataTableColumnHeader>\n        <DataTableCell>{({ row }) => <button type=\"button\">Open {row.data.id}</button>}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe `key={field}` on each generated column matters. Column state such as width, order, visibility, and sticky placement is tied to the field identity.\n\n## Remote data\n\nRemote tables derive their schema from the first non-empty result the same way local tables do. The descriptor helper above is reusable — the `sampleValue` lets the render prop pick alignment per field:\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"id\" sticky=\"left\" />\n\n  <DynamicColumns<ProductRow>>\n    {({ data }) =>\n      fieldsFromRows(data, (field) => field !== 'id').map(({ field, sampleValue }) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>{labelFor(field)}</DataTableColumnHeader>\n          <DataTableCell {...(typeof sampleValue === 'number' ? { className: 'text-right tabular-nums' } : {})}>\n            {({ cellValue }) => String(cellValue ?? '')}\n          </DataTableCell>\n        </DataTableColumn>\n      ))\n    }\n  </DynamicColumns>\n\n  <DataTableColumn id=\"actions\" sticky=\"right\" />\n</DataTable>\n```\n\nThe headers appear after the first non-empty result resolves. Later pages or viewport fetches do not add more columns — that avoids mid-scroll layout changes, but it means the first captured result must contain every field you want to show.\n\nWhen the backend can return column metadata alongside the rows, prefer that. Metadata can render headers before any rows arrive, works for empty responses, and can carry labels, alignment, and visibility hints out of band.\n\n## Placeholders\n\nThe field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data. Placeholder-only fields show up in the discovered column list, and fields that exist only on real rows go missing if the first captured result is all placeholders.\n\nMake placeholders schema-compatible with real rows:\n\n```ts\nconst placeholder = {\n  id: '',\n  product: '',\n  category: '',\n  stock: null,\n}\n```\n\nDo not put placeholder-only fields in the visible schema. If a placeholder needs technical fields for rendering, filter them with `includeField`. If the real field list cannot be known until the backend responds, return column metadata from the backend or wait for a real data result instead of relying on placeholder shape.\n\n## Grouped data\n\nGrouped models mix group-header rows and body rows in the same array. The field scanner treats them the same way, so any field on a group-header row (a `groupLabel`, a level number, a count) would become a body column unless you filter group rows out first:\n\n```ts\nconst fields = fieldsFromRows(\n  data.filter((row) => !(typeof row === 'object' && row !== null && 'groupLabel' in row)),\n  (field) => field !== 'id'\n)\n```\n\nRender group labels with `DataTableGroupHeader`; keep runtime columns focused on ordinary body rows.\n\n## Rediscovering columns\n\n`DynamicColumns` is intentionally one-shot. A user action that changes row values should not reshuffle the table's columns.\n\nWhen a user intentionally switches to a different dataset shape, remount the table:\n\n```tsx\n<DataTable key={datasetId} model={model}>\n  <DynamicColumns>{/* declare columns from the new first result */}</DynamicColumns>\n</DataTable>\n```\n\nA user-facing column-management UI — letting the user add, remove, or reorder fields after mount — is a different problem. Keep the field list in your own React state and map it to `DataTableColumn` declarations directly; `DynamicColumns` doesn't help here, and trying to bend it to that shape leads to the temporal bugs the one-shot rule was designed to avoid.\n\n## When this does not fit\n\n- **Backend-defined schemas.** Return column metadata alongside the rows when headers must render before data, when valid responses can be empty, or when labels and alignment come from server configuration.\n- **Statically-known schemas.** Use a static field list when the schema is known in code but the JSX would be repetitive — see [Generated columns](/data-table/columns/defining-columns/#generated-columns).\n- **Runtime column-management UI.** Lift the field list into your own React state when the user can add, remove, or reorder fields after mount.\n\n`DynamicColumns` fits when the schema is data-defined, the first non-empty result is representative, and remounting the table is acceptable for re-discovery.\n","packages/data-table/docs/3.columns/09.column-layout.md":"---\ntitle: Column Layout\ndescription: Set base column widths and choose which columns grow into extra horizontal space.\nsidebar:\n  label: Column Layout\n---\n\nColumn width has two parts: the base width and the realized width.\n\nThe base width comes from the column header. The table measures `DataTableColumnHeader`, then uses that measured width as the starting point for both the header and every body cell in that column.\n\nThe realized width is the width the column gets after the table compares all base widths with the viewport. When the viewport is wider than the total base width, extra space is distributed by the table. When the viewport is narrower, columns keep their base widths and the table scrolls horizontally.\n\n## Set Base Widths On Headers\n\nUse header width classes for the minimum useful size of each column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror those widths on `DataTableCell`. Body cells render inside tracks sized by the table. Cell width utilities can fight those tracks and cause clipping or overlap.\n\n## Choose Which Columns Grow\n\nUse `DataTableColumn grow={number}` for columns that should absorb extra viewport width. The value is a ratio, not a CSS flex shorthand.\nOmitting `grow`, or passing `grow={0}`, keeps the column fixed when another visible column grows.\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"description\" grow={3}>\n  <DataTableColumnHeader className=\"min-w-80\">Description</DataTableColumnHeader>\n  <DataTableCell>{DescriptionCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"versions\">\n  <DataTableColumnHeader className=\"w-40\">Versions</DataTableColumnHeader>\n  <DataTableCell>{VersionBadgesCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn id=\"actions\">\n  <DataTableColumnHeader className=\"w-16 justify-center\">Actions</DataTableColumnHeader>\n  <DataTableCell>{ActionsCell}</DataTableCell>\n</DataTableColumn>\n```\n\nIn this layout, `name` and `description` receive all extra width. `description` receives three times as much extra width as `name`. `versions`, `updated`, and `actions` keep their base widths.\n\nChoose grow columns from the local data and workflow. IDs, slugs, checkboxes, action menus, short labels, badges, version counts, timestamps, and compact metadata usually should not grow. Names, titles, descriptions, summaries, messages, notes, paths, and other text-heavy content usually benefit from extra room. The field name is only a clue: a short code-like `name` may be fixed, while a user-authored label column may deserve room.\n\nDo not make every column grow. The point of `grow` is to let useful text columns breathe while compact columns stay compact.\n\n## What Happens Without Grow\n\nTables that do not set `grow` preserve the default auto-fill behavior: when the viewport is wider than the total base width, extra space is distributed evenly across visible columns. This keeps existing tables filling their container without changing every column declaration.\n\nOnce any visible column has a positive finite `grow` value, only grow columns receive leftover width. Non-grow columns keep their base widths.\n\n## There Is No Shrink Prop\n\n`grow` only distributes extra space. It does not shrink columns below their base widths, and there is no `shrink` table API.\n\nWhen the viewport narrows, grow space disappears first. After the total base width no longer fits, the table keeps base widths and uses horizontal scrolling.\n\n## Resizing Grow Columns\n\nColumn resizing sets the rendered width of that column. During and after a resize override, the table preserves the current rendered widths of the other columns instead of redistributing grow space on every pointer move. That keeps the resize handle under the pointer and lets wider columns push the table into horizontal overflow.\n\nClear the resize override to return the column to its measured base width and the normal `grow` distribution.\n\nPersist resize overrides as user-selected widths, not as automatic grow output. The saved width is the user's explicit override; automatic grow space is recalculated only when no resize override is active for that layout.\n","packages/data-table/docs/3.columns/index.md":"---\ntitle: Overview\ndescription: Define the visible columns, headers, and cells that turn model rows into a grid.\nsidebar:\n  order: 0\n  label: Overview\n---\n\nColumns decide which fields are visible, in what order, at what width, and how each cell is rendered. You declare them as JSX children of `DataTable` — one `DataTableColumn` per visible field, with its header and cell renderer inside.\n\nThe JSX reads like a static description of the table, but `DataTableColumn` and its children don't render in place. Each one registers a definition with the table, which then draws the virtualized header row and the visible body cells from those definitions. That indirection is what makes virtualization possible: a single cell renderer is reused across many row positions as the user scrolls, and headers compose cleanly with sticky pinning, resizing, slots, and visibility.\n\n## Basic columns\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium\">{row.data.name}</span>\n              <span className=\"text-xs text-muted-foreground\">{row.data.id}</span>\n            </div>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : 'Healthy'}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What each part defines\n\n- `DataTableColumn` — one column, keyed by `field`.\n- `DataTableColumnHeader` — header content and header class.\n- `DataTableCell` — body-cell renderer and cell class.\n\nOmit the header to fall back to the field name; omit the cell to fall back to the field value.\n\n`field` is both the cell lookup key (`row.data[field]`) and the column's persistent identity. Keep it stable across releases if you use any column features that save state — see [State Persistence](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the rules around renaming.\n\nFor layout, put base widths on `DataTableColumnHeader` and use `DataTableColumn grow={...}` only for columns that should absorb extra horizontal space. See [Column Layout](/data-table/columns/column-layout/) for the sizing model and guidance on choosing fixed vs growing columns from the data being displayed.\n\n## Where to go next\n\nDeclarative shape:\n\n- [Defining Columns](/data-table/columns/defining-columns/) — `field`, default rendering, declaration order, visibility, generated columns.\n- [Formatting Cells and Headers](/data-table/columns/cell-and-header-renderers/) — render params, extracted renderer functions, custom header content.\n- [Column Layout](/data-table/columns/column-layout/) — base widths, `grow` ratios, fixed metadata columns, and horizontal overflow.\n- [Column Groups](/data-table/columns/column-groups/) — wrap adjacent columns under a shared header.\n- [Sticky Columns](/data-table/columns/sticky-columns/) — pin a column to the left or right edge during horizontal scrolling.\n\nInteractive layout:\n\n- [Column Visibility](/data-table/columns/column-visibility/) — declarative defaults, runtime toggling, \"Columns\" picker.\n- [Column Resizing](/data-table/columns/column-resizing/) — drag handle in the header, presets, auto-fit.\n- [Column Reordering](/data-table/columns/column-reordering/) — drag-and-drop individual columns or whole groups.\n\nDynamic schemas:\n\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","packages/data-table/docs/4.grouped-rows.md":"---\ntitle: Grouped Rows\ndescription: Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.\nsidebar:\n  label: Grouped Rows\n---\n\nWhen records belong to natural sections — products by category, invoices by customer, employees by department — group headers split the scrolling row stream into visible groups. Users see one continuous list; the headers anchor to the top of each section as new rows scroll into view.\n\n(This page is about row-level grouping. For grouping in the column header row, see [column groups](/data-table/columns/column-groups/).)\n\n## Letting users change groups\n\nThe common case: a \"Group by\" control where the user picks the grouping at runtime. Click the buttons in this example to regroup the table:\n\n```tsx live\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ninterface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  status: 'In stock' | 'Low stock'\n}\n\ninterface ProductGroup {\n  label: string\n}\n\ntype GroupBy = 'none' | 'category' | 'status'\n\nconst products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', status: 'In stock' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office', status: 'Low stock' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-004', name: 'Mechanical Keyboard', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-005', name: 'Studio Monitor', category: 'Audio', status: 'Low stock' },\n  { id: 'SKU-006', name: 'Audio Interface', category: 'Audio', status: 'In stock' },\n]\n\nfunction isProduct(row: Product | ProductGroup): row is Product {\n  return 'id' in row\n}\n\nfunction rowKey(row: Product | ProductGroup) {\n  return isProduct(row) ? row.id : `group-${row.label}`\n}\n\nfunction groupProducts(rows: Product[], groupBy: GroupBy) {\n  if (groupBy === 'none') {\n    return rows\n  }\n\n  const buckets = new Map<string, Product[]>()\n  for (const row of rows) {\n    const key = groupBy === 'category' ? row.category : row.status\n    const bucket = buckets.get(key)\n    if (bucket) {\n      bucket.push(row)\n    } else {\n      buckets.set(key, [row])\n    }\n  }\n\n  const data: (Product | ProductGroup)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const [key, items] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${key} (${items.length})` })\n    data.push(...items)\n  }\n\n  return { data, groups }\n}\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = (actionState?.group?.payload ?? 'category') as GroupBy\n\n  const options: { value: GroupBy; label: string }[] = [\n    { value: 'none', label: 'No groups' },\n    { value: 'category', label: 'Group by category' },\n    { value: 'status', label: 'Group by status' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <button\n          aria-pressed={groupBy === option.value}\n          className={`rounded-md border px-3 py-1 text-sm ${groupBy === option.value ? 'bg-foreground text-background' : 'bg-background'}`}\n          key={option.value}\n          onClick={() => dispatch({ action: 'group', payload: option.value })}\n        >\n          {option.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['group'],\n      initialActions: [{ action: 'group', payload: 'category' }],\n      actions: {\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <GroupByControls engineRef={engineRef} />\n      <DataTable\n        className=\"rounded-xl\"\n        computeRowKey={({ data }) => rowKey(data as Product | ProductGroup)}\n        engineRef={engineRef}\n        model={model}\n        style={{ height: 320 }}\n      >\n        <GroupHeaderCell className=\"bg-muted/60 px-3 py-2 text-sm font-medium\">\n          {({ row }) => String((row.data as ProductGroup).label)}\n        </GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nThe interactive grouping above does three things:\n\n- **Declares a `group` pipeline stage** on the model so the table knows where in its data pipeline regrouping runs. The pipeline lists the stages in order; `['group']` is the simplest case, but `['filter', 'sort', 'group']` is the typical production shape.\n- **Defines a `group` action** whose handler receives the current data and a payload (the user's selection) and returns the new `{ data, groups }` shape.\n- **Connects the controls through the table engine.** The buttons publish to `dispatchModelAction$`; the active button reads `modelActionState$`. The toolbar never stores a second copy of the grouping state.\n\nThe wire-up, isolated from the demo's rendering code, looks like this:\n\n```tsx\nconst [model] = useState(() =>\n  localModel<Product, ProductGroup>({\n    data: products,\n    pipeline: ['group'],\n    initialActions: [{ action: 'group', payload: 'category' }],\n    actions: {\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n      },\n    },\n  })\n)\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = actionState?.group?.payload ?? 'category'\n\n  return (\n    <button aria-pressed={groupBy === 'category'} onClick={() => dispatch({ action: 'group', payload: 'category' })}>\n      Group by category\n    </button>\n  )\n}\n```\n\nFor the full filter + sort + group combination — three controls sharing one pipeline — see [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for the runnable end-to-end recipe.\n\n## Pre-grouped data\n\nWhen the grouping is fixed at mount and won't change at runtime, skip the pipeline action and pass the already-flattened result directly:\n\n```tsx\nconst tableData = {\n  data: [\n    { label: 'Office (2)' },\n    { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n    { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  ],\n  groups: [{ index: 0, level: 0 }],\n}\n\nconst model = localModel(tableData)\n```\n\n`GroupHeaderCell` receives `row.data` for the marked entry, so it can read whatever fields you put on the group object (here, `label`).\n\n## How grouping is expressed\n\nThe table doesn't accept nested children. It receives one flattened row array plus markers identifying which entries are group headers:\n\n```ts\ntype GroupedRows<TData, TGroup> = {\n  data: Array<TData | TGroup>\n  groups: { index: number; level: number }[]\n}\n```\n\n- `data` mixes group rows and data rows in display order.\n- `groups[]` marks which positions in `data` are headers. Each marker carries an `index` (zero-based position in the flattened array) and a `level` (nesting depth).\n- The table renders marked entries through `GroupHeaderCell` instead of through column cells.\n\nGroup rows only need the fields your `GroupHeaderCell` reads. Data rows need the fields your columns read. The two shapes can differ; the table picks the renderer per entry based on the markers.\n\n## Multi-level grouping\n\nFor deeper hierarchy (region → category → product), use sequential markers with increasing levels:\n\n```ts\nconst groups = [\n  { index: 0, level: 0 }, // 'North America'\n  { index: 1, level: 1 }, //   'Office'\n  { index: 4, level: 1 }, //   'Peripherals'\n  { index: 7, level: 0 }, // 'Europe'\n  { index: 8, level: 1 }, //   'Office'\n]\n```\n\nHierarchy is implicit in row order: every level-1 marker belongs to the most recent level-0 marker that precedes it. Style nesting visually by branching on `row.level` inside `GroupHeaderCell`:\n\n```tsx\n<GroupHeaderCell>\n  {({ row }) => (\n    <div className={row.level === 0 ? 'bg-muted font-medium' : 'bg-muted/40 pl-6'}>{(row.data as { label: string }).label}</div>\n  )}\n</GroupHeaderCell>\n```\n\n`level` controls visual and sticky nesting only — it does not add parent-child data behavior. If you need to know which level-0 group a row belongs to, derive it from row order at the time you flatten.\n\n## Remote grouped data\n\nWhen the grouping is computed on the backend, the fetch result includes markers alongside the rows; `remoteModel` forwards them to the table. The backend returns a flattened grouped result, not nested children:\n\n```ts\nasync function fetchProducts(params: FetchParams<Query>) {\n  const result = await fetchGroupedRows(params)\n\n  return {\n    rows: result.rows,\n    groups: result.groups,\n    totalCount: result.totalCount,\n  }\n}\n```\n\nIn offset mode, `totalCount` is the length of the full flattened result (including group headers), and marker indexes are positions in that full result — not relative to the current page. In append mode, return markers for the row stream after each append. Group rows still need a shape your `GroupHeaderCell` can branch on.\n\n## Rules of thumb\n\n- Group rows are virtualized full-width rows — keep their content lightweight.\n- Give group rows stable keys via `computeRowKey` when grouping, sorting, or filtering can reorder them. Use a stable group identifier (`groupKey`, `region`, etc.), not the current group index.\n- The table doesn't compute groups for you. Either group your data before passing it in, or compute inside a pipeline stage.\n- `level` controls visual and sticky nesting. It does not add parent-child data behavior.\n\n## When this doesn't fit\n\n- **Nested source data.** Flatten it before passing to the table — the table's display contract is flat-with-markers, not nested children.\n- **User-toggleable expand/collapse.** Keep the expanded/collapsed state in your app and rebuild the flattened `data` and `groups` from it on each toggle.\n- **Remote grouping across unloaded ranges.** Compute the grouping on the backend, or return enough metadata for the client to place group headers correctly without seeing every row.\n\nSee [Group Rows by Region and Category](/data-table/examples/grouped-data/) for a complete pre-grouped local table, and [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for grouping wired alongside filter and sort.\n","packages/data-table/docs/5.state-persistence.md":"---\ntitle: State Persistence\ndescription: Persist table feature state and model action state with opt-in adapters.\nsidebar:\n  label: State Persistence\n---\n\n`DataTableStatePersistence` saves column width, order, visibility, and model action state under a storage key, restoring them on mount. With it wired up, a user's resize, hide, drag, or filter survives a reload. Two requirements: mount the component inside the table, and pass an adapter for each feature you want persisted.\n\n## Minimal setup\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columns$, localModel, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport {\n  columnVisibilityPersistenceAdapter,\n  columnVisibilityState$,\n  setColumnVisibility$,\n} from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence } from '@virtuoso.dev/data-table/state-persistence'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n  { id: 'SKU-004', product: 'Noise-canceling Headset', category: 'Audio', stock: 9, internalId: 'inv-004' },\n]\n\nconst model = localModel({ data: rows })\nconst adapters = [columnVisibilityPersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nfunction ColumnPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div className=\"mb-3 flex flex-wrap gap-2 rounded-md border bg-muted/30 p-3 text-sm\">\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key) ?? column.visible !== false\n        return (\n          <button\n            aria-pressed={visible}\n            className={visible ? 'rounded border bg-background px-2 py-1 font-medium' : 'rounded border px-2 py-1 text-muted-foreground'}\n            key={key}\n            onClick={() => setColumnVisibility({ key, visible: !visible })}\n            type=\"button\"\n          >\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 360 }}>\n      <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-feature-page\" />\n      <ColumnPicker />\n\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"internalId\" visible={false}>\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Internal ID'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nReload after hiding a column or resizing a header — the adapters you passed restore that state.\n\n## Adapters\n\n| Adapter                                | Subpath              | Persists                                                                                 |\n| -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |\n| `modelStatePersistenceAdapter()`       | `/state-persistence` | Action payloads from `localModel()` / `remoteModel()` (filters, sort, group)             |\n| `columnOrderPersistenceAdapter()`      | `/column-reorder`    | Column order, keyed by field                                                             |\n| `columnWidthPersistenceAdapter()`      | `/column-resize`     | Width overrides, keyed by field                                                          |\n| `columnVisibilityPersistenceAdapter()` | `/column-visibility` | Visibility overrides (only those differing from the declarative default), keyed by field |\n\n```tsx\nimport { columnOrderPersistenceAdapter } from '@virtuoso.dev/data-table/column-reorder'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { columnVisibilityPersistenceAdapter } from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nconst adapters = [\n  columnVisibilityPersistenceAdapter(),\n  columnOrderPersistenceAdapter(),\n  columnWidthPersistenceAdapter(),\n  modelStatePersistenceAdapter(),\n]\n\n<DataTable model={model} style={{ height: 420 }}>\n  <DataTableStatePersistence adapters={adapters} storageKey=\"inventory-table\" />\n  {/* columns */}\n</DataTable>\n```\n\nStorage defaults to `window.localStorage`. Pass `storage` for `sessionStorage`, a test double, or an application-specific backend.\n\nThe `adapters` array must be stable — define it outside the component or wrap it in `useMemo()` so the persistence component doesn't resubscribe on every render.\n\n## Storage keys and resets\n\n`storageKey` identifies one logical table. Change it when the saved state should no longer apply (different product surface, breaking schema change).\n\n`resetKey` clears saved state without changing the identity — bump it when the user explicitly resets a view.\n\n```tsx\n<DataTableStatePersistence adapters={adapters} resetKey={resetVersion} storageKey=\"inventory-table\" />\n```\n\nAdapters preserve unmatched entries where they can, so state survives partially-matching dynamic schemas.\n\n## Field names are the persistence contract\n\nThe adapters above all key their saved state by `field`. A column's width entry is \"the width for the column whose `field` is `'price'`\", not \"the width for the column in position 2\". Same for order, visibility, and sticky pin. That's what makes saved state survive schema migrations, partial column lists, and dynamic column generation — the field name is the contract between the column declaration and the saved entry.\n\nTwo consequences fall out of that:\n\n**Don't rename `field` lightly.** A column declared as `field=\"price\"` last week and `field=\"cost\"` this week is two different columns from persistence's perspective. The width entry under `'price'` doesn't transfer to `'cost'`, and the user's saved width silently goes back to the default. For schema migrations that have to rename, plan a one-time migration step that rewrites saved entries from the old key to the new one.\n\n**Keep `field` separate from header copy.** Header text is presentation and changes freely (locale switches, A/B tests, restyling). `field` is identity and must stay the same:\n\n```tsx\n<DataTableColumn field=\"customerName\">\n  <DataTableColumnHeader>{locale === 'de' ? 'Kunde' : 'Customer'}</DataTableColumnHeader>\n</DataTableColumn>\n```\n\nThis separation is why persisted state survives locale changes — the saved width sits under `'customerName'` regardless of what the header reads in any given language.\n\n## Model action persistence\n\nOnly actions marked with `persistence` are saved. Add it to a pipeline action for local models, or to an action that transforms request params for remote models.\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { status: 'all' },\n  actions: {\n    filter: {\n      persistence: { isEmpty: (value) => value === 'all', key: 'status' },\n      handler: ({ params, payload }) => ({ ...params, status: payload }),\n    },\n  },\n})\n```\n\nThe adapter saves action payloads, not row data. On restore, the model replays the action and rebuilds the request or local pipeline result.\n\nSee the [State Persistence example](/data-table/examples/state-persistence/) for a remote table wiring model and column-width persistence together.\n","packages/data-table/docs/6.controlling-the-table.md":"---\ntitle: Controlling the Table\ndescription: Read table state and publish table actions from nearby toolbars, panels, and other application UI.\nsidebar:\n  label: Controlling the Table\n---\n\nExternal UI — toolbars, settings menus, status badges — often needs to read the table's state (current scroll position, visible columns, loading phase) and trigger its actions (scroll to a row, toggle a column, refresh data). The table exposes both through its internal reactive runtime; external UI taps that runtime directly instead of receiving lifted state through props.\n\nTwo pieces wire the connection. The first is an identifier for the table instance: `useEngineRef()` returns a ref for UI in the same component, and a string `engineId` reaches a table mounted elsewhere in the tree. The second is the channels the identifier carries — _state cells_ like `scrollLocation$` and `columns$` expose values you read with `useRemoteCellValue`, and _action streams_ like `scrollToRow$` and `setColumnVisibility$` accept commands you publish with `useRemotePublisher`. The `$` suffix marks both flavors.\n\n## Engine refs\n\nReach for `engineRef` when the external UI lives in the same component as the table — a toolbar two divs up, a status panel rendered as a sibling. `useEngineRef()` returns an opaque ref; pass it to `<DataTable engineRef={engineRef}>` and to the remote hooks beside it, and they resolve to the same engine.\n\n```tsx live\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, scrollLocation$, scrollToRow$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 80, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 81\n        </Button>\n      </div>\n\n      <div className=\"rounded-lg border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n        {scrollLocation === undefined ? 'Waiting for scroll state...' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 360 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nMount order doesn't matter. The hooks register against the ref immediately and start receiving values as soon as the table's engine comes up.\n\n## Engine IDs\n\nReach for `engineId` when the external UI lives _far_ from the table — a page-level toolbar in the layout shell, a sibling panel that doesn't share a parent component with the table, anything where threading a ref through props would mean prop-drilling through unrelated levels. Pick a stable string, pass it as `<DataTable engineId={…}>`, and hand the same string to the hooks anywhere in the tree.\n\n```tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { scrollToRow$, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst TABLE_ENGINE_ID = 'inventory-table'\n\nfunction PageToolbar() {\n  const scrollToRow = useRemotePublisher(scrollToRow$, TABLE_ENGINE_ID)\n\n  return (\n    <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n      Top\n    </Button>\n  )\n}\n\nfunction InventoryTable({ rows }) {\n  return (\n    <DataTable model={model} engineId={TABLE_ENGINE_ID} style={{ height: 420 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nUse a unique ID per table on a given screen (`inventory-table`, `transactions-table`). The registry holds one engine per ID, so a second table mounting with the same `engineId` silently overwrites the first — and when the first unmounts, the registry entry is cleared out from under the second. The symptoms are confusing; the prevention is trivial.\n\n## Firing model actions from external UI\n\nFiltering, sorting, grouping, and search often live in a toolbar beside the table. Keep that toolbar connected to the model through the table engine: publish the user action with `dispatchModelAction$`, then read `modelActionState$` to decide which control is active. The toolbar does not need the model handle, and it does not need to keep a separate React state value in sync with the model.\n\n```tsx live\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ntype StatusFilter = 'all' | 'active' | 'paused'\n\nconst rows = [\n  { id: 'acct-1', account: 'Northwind', status: 'active' },\n  { id: 'acct-2', account: 'Contoso', status: 'paused' },\n  { id: 'acct-3', account: 'Fabrikam', status: 'active' },\n]\n\nfunction filterRows(data: typeof rows, status: StatusFilter) {\n  return status === 'all' ? data : data.filter((row) => row.status === status)\n}\n\nfunction StatusControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const selected = (actionState?.status?.payload ?? 'all') as StatusFilter\n\n  const options: { value: StatusFilter; label: string }[] = [\n    { value: 'all', label: 'All' },\n    { value: 'active', label: 'Active' },\n    { value: 'paused', label: 'Paused' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <Button\n          aria-pressed={selected === option.value}\n          key={option.value}\n          onClick={() => dispatch({ action: 'status', payload: option.value })}\n          variant={selected === option.value ? 'default' : 'outline'}\n        >\n          {option.label}\n        </Button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel({\n      data: rows,\n      pipeline: ['status'],\n      initialActions: [{ action: 'status', payload: 'all' }],\n      actions: {\n        status: {\n          stage: 'status',\n          handler: ({ data, payload }) => filterRows(data, payload as StatusFilter),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <StatusControls engineRef={engineRef} />\n      <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} engineRef={engineRef} model={model} style={{ height: 280 }}>\n        <DataTableColumn field=\"account\">\n          <DataTableColumnHeader>Account</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n`modelActionState$` tracks stateful model actions: the current filter, sort, group, search, or other action payload that represents a user choice. Command-like actions are different. `refresh`, `cancel`, `loadMore`, source mutators, and similar commands can be dispatched through `dispatchModelAction$`, but they are not remembered in `modelActionState$` because they do not describe the current toolbar state.\n\nThe bridge blocks only model lifecycle names owned by the table itself: `handshake`, `disconnect`, and `viewportChange`.\n\n## Common state and actions\n\nThe cells and streams most external UI reaches for, grouped by the hook they pair with.\n\nRead with `useRemoteCellValue`:\n\n- `columns$`, `visibleColumns$`, `columnWidths$` — column shape and layout\n- `scrollLocation$`, `scrollerElement$`, `itemHeight$` — scroll state and DOM references\n\nPublish with `useRemotePublisher`:\n\n- `scrollToRow$`, `scrollIntoView$`, `cancelSmoothScroll$` — scroll commands\n- `setColumnSticky$` — pin a column at runtime\n\nFeature modules contribute their own: `resizeColumn$` and friends from `@virtuoso.dev/data-table/column-resize`, `reorderColumns$` from `column-reorder`, `setColumnVisibility$` from `column-visibility`. Each feature page documents the cells and streams it adds.\n\nInside the table — in a cell renderer, in a custom row component — use the local hooks instead: `useVirtuosoLocation()`, `useCurrentlyRenderedData()`, `useVirtuosoLoadingState()`. They read the same underlying state but resolve the engine from React context, so no ref or ID is needed.\n\nFor DOM-level integrations, prefer `scrollerElement$` and `itemHeight$` over `document.querySelector`. The cells stay in sync with React's mount and unmount; a query selector can return a node from a torn-down render or miss a remount entirely.\n\n## Scroll lifecycle props\n\nOne-way notifications and first-render positioning live on the component, not the engine:\n\n- `initialLocation` — scrolls to a row when the table first renders with data\n- `onScroll` — receives the current `ListScrollLocation`\n- `onRenderedDataChange` — receives the rendered data rows, excluding group rows\n\n```tsx\n<DataTable\n  model={model}\n  initialLocation={{ index: 80, align: 'center' }}\n  onRenderedDataChange={(renderedRows) => {\n    console.log(renderedRows.map((row) => row.id))\n  }}\n  onScroll={(location) => {\n    console.log(location.scrollTop)\n  }}\n>\n  {/* columns */}\n</DataTable>\n```\n\nUse props when the parent only needs to observe — log scroll position, fire analytics, set an initial location once. Reach for the remote hooks when UI elsewhere in the tree needs to subscribe to the same updates or push commands back; props can't carry a subscription out to a sibling component.\n\n## Overscan controls\n\nA pair of static-tuning props that, like the scroll lifecycle props above, live on the component rather than on the engine — overscan is a per-table policy, not something external UI flips at runtime. The defaults are good for typical row heights and cell complexity; raise them only when you can see the table struggling.\n\n- `increaseViewportBy` — extra vertical pixels rendered before and after the viewport. Raise when rows contain images that need to warm up before they scroll into view, or when each row's render cost is high enough that the user can outrun the scheduler.\n- `columnOverscanCount` — extra columns rendered on each side of the horizontal viewport. Raise on wide tables that feel jumpy during horizontal scroll, especially when cells contain charts or non-trivial markup.\n\nBoth numbers cost render work even when the user isn't scrolling, so bump them in small steps and stop as soon as the symptom is gone.\n","packages/data-table/docs/7.customization/01.styling.md":"---\ntitle: Styling Cells, Headers, and Rows\ndescription: Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.\nsidebar:\n  label: Styling\n---\n\nThe visible parts of the table — cells, headers, the outer frame — all accept Tailwind classes the way any component in your app does. Reach for column-level styling when a single column needs to look different (a status pill, a right-aligned number, a muted secondary line) and wrapper-level styling when the change applies to the whole table.\n\n## Column-level styling\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', status: 'Launch ready', theme: 'Office' },\n  { name: 'USB-C Dock', status: 'Low stock', theme: 'Peripherals' },\n  { name: 'Noise-canceling Headset', status: 'Backorder', theme: 'Audio' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"theme\">\n        <DataTableColumnHeader>Theme</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-slate-100 px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => (\n            <span\n              className={\n                cellValue === 'Backorder'\n                  ? 'font-medium text-red-600'\n                  : cellValue === 'Low stock'\n                    ? 'font-medium text-amber-600'\n                    : 'font-medium text-emerald-600'\n              }\n            >\n              {String(cellValue)}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n`className` on `DataTableCell` styles the positioned cell. A column header has two widths, so it exposes two styling surfaces:\n\n- `DataTableColumnHeader.className` styles the intrinsic header content and sets the column's base width.\n- `DataTableColumnHeader.containerProps` styles the full rendered column width and accepts event handlers and ARIA attributes.\n\nThe table measures the intrinsic surface before it distributes spare width through `grow`. Measuring the rendered surface instead would feed the grown width back in as the next base width, so the column could not shrink with its viewport. `containerProps` provides a separate full-width paint and interaction surface without changing that measurement.\n\nUse `containerProps` when a background, hover state, outline, box shadow, cursor, or click target must cover the spare width:\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-64\" containerProps={{ className: 'cursor-pointer hover:bg-muted/50' }}>\n    Product\n  </DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nKeep layout on `className`. The table owns the outer container's geometry, so width, flex, margin, padding, border, display, and `justify-*` styles on `containerProps` can clip content or move edge-aligned slots. Use `box-shadow` or `outline` instead of a border on that surface, and use `HeaderEnd` for controls that must align to the rendered edge.\n\nContainer events receive bubbled events from nested controls and header slots. If the header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nRender-function children still let you wrap only the value when it needs its own markup, such as a badge or two-line label.\n\n## Wrapper-level styling\n\n`className` on `<DataTable>` reaches the outer frame around the scroll viewport. The shadcn wrapper already provides the default frame (`rounded-md border`) and table surface, so you do not need to repeat `rounded-md border` at each instance. Use wrapper classes for intentional frame overrides, shadows, and table-level CSS variable overrides.\n\n```tsx\n<DataTable className=\"rounded-xl shadow-sm\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nTo remove or strengthen the default frame, override it directly:\n\n```tsx\n<DataTable className=\"border-0\" model={model}>\n  {/* columns */}\n</DataTable>\n\n<DataTable className=\"border-2\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nFor pixel sizing, prefer `style` (`style={{ height: 360 }}`) — the table needs a measurable height to virtualize, and Tailwind's `h-*` utilities work too, as long as the resolved height is real (not a percentage of an unmeasured parent).\n\n## What this page doesn't cover\n\nTwo adjacent concerns have their own pages:\n\n- **Replacing the table's structural components** (Row, GroupHeader, sticky wrappers, scroll element) lives in [Replacing Internals](/data-table/customization/replacing-internals/). That's the `components` prop pattern.\n- **Default classes baked into the shadcn wrapper** — change those by editing `@/components/ui/data-table` directly. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/) for the wrapper anatomy.\n","packages/data-table/docs/7.customization/02.replacing-internals.md":"---\ntitle: Replacing Internals\ndescription: Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.\nsidebar:\n  label: Replacing Internals\n---\n\nMost styling you do to a table is column-level — a class on a cell, a renderer that builds a status pill. But some changes apply across every row at once: hover states, density, dividers, the markup around sticky columns, the element that actually scrolls. Those changes live in the table's structural slots, and you change them by passing a replacement component.\n\nThe `components` prop is the channel. Each key in `components` swaps out one piece of the table's internal markup with your own component.\n\n```tsx\nimport * as React from 'react'\nimport type { RowComponentProps } from '@virtuoso.dev/data-table'\n\nconst components = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b transition-colors hover:bg-muted/50\" {...props} />\n  }),\n}\n\n;<DataTable components={components} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nThe override receives the same props the default `<div>` would have received — class, style, children, data attributes, the table's `context` value. Forward them all, or the table loses track of the element. The forwarded `ref` is what the virtualizer reads sizes from, so passing it to the root element is mandatory; the table will appear broken without it.\n\n## What you can replace\n\nThe `components` prop covers structural slots that appear repeatedly or wrap whole regions:\n\n- `Row` — the element used for data rows (one per visible row). Hover states, dividers, alternating backgrounds, density, row-level transitions.\n- `StickyHeader` — the wrapper around the sticky header row. Backgrounds and borders that need to match your page chrome instead of the default.\n- `StickyColumnContainer` — the wrapper around each sticky column region, used in both header and body. The `data-sticky` attribute on the props tells you whether it's the left or right side.\n- `LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter` — the three loading-state slots. Covered separately in [Empty and Loading States](/data-table/customization/empty-and-loading-states/) because each one has its own narrative.\n\n```tsx\nimport type { DataTableComponents, RowComponentProps, StickyColumnContainerComponentProps } from '@virtuoso.dev/data-table'\n\nconst components: DataTableComponents = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b\" {...props} />\n  }),\n  StickyColumnContainer: React.forwardRef<HTMLDivElement, StickyColumnContainerComponentProps>(function Sticky(props, ref) {\n    const side = props['data-sticky']\n    return <div ref={ref} className={side === 'left' ? 'bg-card shadow-[1px_0_0_0_hsl(var(--border))]' : 'bg-card'} {...props} />\n  }),\n}\n```\n\n## Two slots that aren't in `components`\n\nA few replacements have their own top-level props instead of living under `components`:\n\n- `EmptyPlaceholder` — rendered when the model has zero rows. Top-level prop because most apps customize it and want a single explicit place to look.\n- `ScrollElement` — the element that actually scrolls. Top-level prop because it interacts with viewport ownership (`useWindowScroll`, `customScrollParent`) — see [Scroll Containers](/data-table/customization/scroll-containers/).\n\nBoth follow the same forward-the-props rule.\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n;<DataTable EmptyPlaceholder={EmptyPlaceholder} ScrollElement={ScrollElement} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## When to edit the wrapper instead\n\nThe shadcn wrapper at `@/components/ui/data-table` already supplies defaults for every slot. Two questions decide where your override belongs:\n\n- **Does every table in your app want this change?** Edit the default in the wrapper file. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/).\n- **Does this one table want something different?** Pass `components` (or `EmptyPlaceholder` / `ScrollElement`) on that table only.\n\nBoth routes hit the same slots; the difference is scope.\n\n## Don't reach for `data-testid` as a styling hook\n\nPer the data-table conventions, `data-testid` is reserved for tests. When your override needs a styling hook, use a semantic data attribute (`data-density=\"compact\"`) or a className — not a test ID. The table itself never styles against `data-testid` and you shouldn't either.\n","packages/data-table/docs/7.customization/03.empty-and-loading-states.md":"---\ntitle: Empty, Loading, and Error States\ndescription: Customize empty placeholders and remote loading states without changing data model.\nsidebar:\n  label: Empty and Loading States\n---\n\nThe table has three \"no rows\" states: it has zero rows to display (empty), it's loading rows that haven't arrived yet (loading), or a load failed (error). Each state has its own slot component, and the shadcn wrapper ships sensible defaults for all of them — override a slot when the default copy doesn't fit your domain.\n\n## Empty placeholder\n\n`EmptyPlaceholder` renders when the model has zero rows. The component receives the table `context` prop.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel<{ name: string }>({ data: [] })\n\nfunction EmptyState({ context }: { context: { query: string } }) {\n  return <div className=\"p-6 text-sm text-muted-foreground\">No products match \"{context.query}\".</div>\n}\n\nexport default function App() {\n  return (\n    <DataTable EmptyPlaceholder={EmptyState} className=\"rounded-xl\" context={{ query: 'archived' }} model={model} style={{ height: 240 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Remote loading and error state\n\n`remoteModel()` drives three slot components:\n\n- `LoadingPlaceholder` — `initial` phase (first unresolved dataset)\n- `LoadingOverlay` — `refresh` phase (request params changed, old rows still visible)\n- `LoadingFooter` — `end` phase (append mode is fetching more rows below)\n\nOffset viewport fetches show up as placeholder rows, not a table-level loading slot. `DataTableLoadingState` exposes a `start` segment for custom integrations, but the built-in `remoteModel()` never sets it.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }: LoadingComponentProps) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nThe shadcn wrapper ships defaults for all three slots. Override them for different copy, skeletons, or error recovery controls.\n\nExternal UI reads the same state through `useVirtuosoLoadingState()` (inside the table) or `loadingState$` with remote engine hooks (outside).\n\n## Error recovery\n\nErrors attach to the phase that failed, so a first-load failure can be handled differently from a refresh failure:\n\n```tsx\nfunction LoadingPlaceholder({ loadingState }: LoadingComponentProps) {\n  if (loadingState.initial.status === 'error') {\n    return (\n      <div className=\"p-6 text-sm\" role=\"status\">\n        Could not load products. Check your connection and try again.\n      </div>\n    )\n  }\n\n  return <div className=\"p-6 text-sm text-muted-foreground\">Loading products...</div>\n}\n\nfunction LoadingFooter({ loadingState }: LoadingComponentProps) {\n  if (loadingState.end.status === 'error') {\n    return <div role=\"status\">Could not load more rows.</div>\n  }\n\n  return loadingState.end.status === 'loading' ? <div>Loading more...</div> : null\n}\n\n;<DataTable components={{ LoadingFooter, LoadingPlaceholder }} model={model} />\n```\n\nRetry buttons belong with the slot that's showing the error: initial failure → retry from the placeholder, refresh failure → retry from the overlay, append failure → retry from the footer.\n","packages/data-table/docs/7.customization/04.scroll-containers.md":"---\ntitle: Scroll Containers\ndescription: Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.\nsidebar:\n  label: Scroll Containers\n---\n\nThe default table renders its own scrolling element. Three props change that — pick one when the table shares a viewport with surrounding page chrome, or when virtualization needs to be driven by a different scrolling element.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 80 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\n  return (\n    <div ref={setScrollParent} className=\"h-[360px] overflow-auto rounded-xl border\">\n      <div className=\"sticky top-0 z-10 border-b bg-background px-3 py-2 text-sm font-medium\">Inventory workspace</div>\n      <DataTable computeRowKey={({ data }) => data.id} customScrollParent={scrollParent} increaseViewportBy={360} model={model}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Fill available panel height\n\nFor app pages where the table should consume the remaining space below fixed page chrome, keep the default internal table scroller and give the table a measured flex slot. This is usually better than guessing a pixel height.\n\nThe important pieces are:\n\n- a parent with a real height, such as a route shell, panel, or card body\n- `min-h-0` on each flex ancestor between that measured parent and the table\n- `shrink-0` on controls above the table\n- `flex-1 min-h-0` on `DataTable`, plus `style={{ height: '100%' }}`\n\n```tsx\nexport function OrdersPage() {\n  return (\n    <section className=\"flex h-full min-h-0 flex-col\">\n      <PageHeader className=\"shrink-0\" />\n\n      <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n        <Toolbar className=\"shrink-0\" />\n\n        <DataTable className=\"min-h-0 flex-1\" computeRowKey={({ data }) => data.id} model={model} style={{ height: '100%' }}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Order</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </section>\n  )\n}\n```\n\nUse this pattern only when the parent is height-constrained. If the table lives in normal document flow, keep a fixed table height (`style={{ height: 360 }}`) or intentionally switch to `useWindowScroll` / `customScrollParent`.\n\n## Window scrolling\n\n`useWindowScroll` makes the document viewport drive the table's scroll state:\n\n```tsx\n<DataTable model={model} useWindowScroll>\n  {/* columns */}\n</DataTable>\n```\n\nThe table no longer renders an internal scroller; its height comes from rendered content and the surrounding page layout. Sticky headers now interact with document-level sticky offsets, so test them against your actual header/nav stack.\n\n## Existing parent element\n\n`customScrollParent` attaches the table to an existing scrolling element:\n\n```tsx\nconst [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\nreturn (\n  <div ref={setScrollParent} className=\"h-[480px] overflow-auto\">\n    <Toolbar />\n    <DataTable customScrollParent={scrollParent} model={model}>\n      {/* columns */}\n    </DataTable>\n  </div>\n)\n```\n\n## Custom scroll element\n\n`ScrollElement` replaces the default scroller component while keeping the rest of the table contained:\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n<DataTable ScrollElement={ScrollElement} model={model} />\n```\n\nPick one scroll mode per table. For `customScrollParent`, make sure that element has a real height and is the element that actually scrolls.\n","packages/data-table/docs/7.customization/05.ambient-context.md":"---\ntitle: Ambient Context\ndescription: A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.\nsidebar:\n  label: Ambient Context\n---\n\nThe `context` prop is the table's _ambient context_: a single bag of values that's available throughout the table's customizable parts — `EmptyPlaceholder`, the loading components, `ScrollElement`, `computeRowKey`, structural component overrides. You set it once on `<DataTable>` and the table passes it to every callback and component override that reads it, without per-callback prop wiring.\n\nTypical contents are values the surrounding screen owns but the table's slots need to read:\n\n- the current search query, so `EmptyPlaceholder` can render \"no results for _archived_\"\n- the active tenant ID, so `computeRowKey` can scope keys across saved views\n- a sticky-header offset the parent layout controls, so a custom `ScrollElement` can honor it\n\n```tsx\nfunction EmptyPlaceholder({ context }) {\n  return <div>{context.emptyMessage}</div>\n}\n\n;<DataTable\n  EmptyPlaceholder={EmptyPlaceholder}\n  computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`}\n  context={{ emptyMessage: 'No archived products', tenantId: 'acme' }}\n  model={model}\n>\n  {/* columns */}\n</DataTable>\n```\n\nCompared to wiring each callback to its own props or closing over surrounding state, `context` is the right channel when several internals consume the same value (a search query the empty state needs to display _and_ the loading state references) or when the value comes from above the table component (a router param, an auth scope, a feature flag).\n\n## What receives context\n\nThe same `context` value reaches:\n\n- [`computeRowKey`](/data-table/data-model/row-keys/) — for scoping row keys across tenants, saved views, or table instances that might share row IDs.\n- `EmptyPlaceholder` — for the failed-search query, the filter that returned zero rows, or recovery copy.\n- Loading slots (`LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter`) — for status copy or retry handlers that depend on screen state.\n- Row component overrides — for density flags, alternating-row styling toggles, hover-state preferences.\n- Sticky wrapper overrides — for sticky background colors that match the surrounding chrome.\n- Custom scroll elements — for layout flags, scrollbar styling, or whether to honor a sticky header height.\n\n## What doesn't receive context\n\nCell and header renderers don't receive `context`. They run inside the table's column-virtualization loop, where threading context through every cell on every scroll would be a measurable cost in a wide table. For values cells need to read, close over screen-local state directly when the renderer is inline, or use React context (`useContext`) when the renderer is extracted:\n\n```tsx\nconst FilterContext = React.createContext<string>('')\n\nfunction ProductCell({ row }: CellRenderParams) {\n  const filter = React.useContext(FilterContext)\n  return <span>{highlight(row.data.name, filter)}</span>\n}\n```\n\nThe two channels — React's `useContext` and the table's `context` prop — coexist cleanly: React context for what cell renderers need, the table's `context` prop for what infrastructure needs.\n\n## When to use context vs. closures\n\nFor values defined right above the table in the same component, a closure is simpler:\n\n```tsx\nfunction ProductsTable({ searchQuery, tenantId }: Props) {\n  return (\n    <DataTable\n      EmptyPlaceholder={() => <div>No products match \"{searchQuery}\"</div>}\n      computeRowKey={({ data }) => `${tenantId}:${data.id}`}\n      model={model}\n    >\n      {/* columns */}\n    </DataTable>\n  )\n}\n```\n\n`context` becomes the better choice when the callbacks are extracted (a shared `EmptyPlaceholder` component lives in a design system, not in this file), when one value is consumed by several callbacks at once, or when the table is rendered from a higher-up shell and the callbacks live further down the tree.\n","packages/data-table/docs/7.customization/06.header-slots.md":"---\ntitle: Header Slots\ndescription: Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.\nsidebar:\n  label: Header Slots\n---\n\nA column header has four slot positions for additional UI. Resize handles, reorder grips, sort buttons, filter menus, and selection toggles all mount through one of them.\n\n```text\n┌──────────────── HeaderOverlay ────────────────┐\n│ [HeaderStart]  Header label  [HeaderEnd]  |Edge│\n└───────────────────────────────────────────────┘\n```\n\n- `HeaderStart` — before the label, in normal flex layout. Typical for buttons and menus that sit alongside the label text.\n- `HeaderEnd` — after the label, in normal flex layout. Same use cases as `HeaderStart`, opposite side.\n- `HeaderEdge` — pinned to the column boundary, outside the label's flex flow. Resize handles use this; the position is independent of label length.\n- `HeaderOverlay` — covers the whole header. Drop indicators and custom overlays use this; the slot paints over both the label and any in-flow slots.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge, HeaderEnd } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14 },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [sortField, setSortField] = React.useState<'product' | 'stock'>('product')\n  const sortedRows = React.useMemo(() => {\n    return [...rows].sort((a, b) => String(a[sortField]).localeCompare(String(b[sortField]), undefined, { numeric: true }))\n  }, [sortField])\n\n  React.useEffect(() => {\n    model.setData(sortedRows)\n  }, [sortedRows])\n\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'product')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'stock')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nSlots stack: a single header can host all four at once. Reorder grip on the left, sort button on the right, resize handle at the edge, drop indicator spanning the whole header for the reorder gesture.\n\n```tsx\nimport { HeaderEdge, HeaderEnd, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\n;<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n    <HeaderEdge component={ResizeHandle} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## The render-function rule\n\nWhen a header contains any slots, the label must be a single render-function child (`{() => 'Product'}`) rather than a plain string. The header needs a function to evaluate alongside the slots; a string child only works for plain headers without slots. See [Cell and Header Renderers](/data-table/columns/cell-and-header-renderers/) for the broader story on render functions.\n\n## What slot components receive\n\nSlot components are passed the column key, the column metadata, the column state, and the header element ref — enough to publish engine actions, render indicators, and position overlays without DOM queries. That ref is what lets a drop-indicator overlay measure the header it's painting over, and what lets an edge-pinned handle compute its drag delta.\n\n## Where this shows up in the shadcn registry\n\nThe opt-in column features ship slot components that drop straight into these positions:\n\n- `ResizeHandle` from `@/components/ui/data-table/column-resize` — mounts in `HeaderEdge`. Install with registry item `data-table-resize-handle`. See [Column Resizing](/data-table/columns/column-resizing/).\n- `ReorderGrip` and `ReorderDropZone` from `@/components/ui/data-table/column-reorder` — mount in `HeaderStart` and `HeaderOverlay` respectively. See [Column Reordering](/data-table/columns/column-reordering/).\n- `SortHeaderButton` from `@/components/ui/data-table/column-sort` — mounts in `HeaderEnd` and dispatches a model `sort` action. Install with registry item `data-table-sort-header-button`. See [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\nCustom sort, filter, menu, and selection controls follow the same pattern: a component that takes the slot's render params and returns whatever JSX you need.\n","packages/data-table/docs/7.customization/07.shadcn-wrapper.md":"---\ntitle: Inside the shadcn Wrapper\ndescription: Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.\nsidebar:\n  label: The shadcn Wrapper\n---\n\nThis page applies to the shadcn install path. If you installed `@virtuoso.dev/data-table` directly without the registry, you own the equivalent components in your own files — the wrapper-vs-headless split still applies, but the file locations are yours to decide. See [Headless Install](/data-table/installation/headless/) for that route.\n\nRunning `npx shadcn add` copied a folder of components into `@/components/ui/data-table`. That folder is your code — it ships in your repo, it's checked into your git history, and editing it doesn't fork the table's behavior. The behavior lives somewhere else.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', owner: 'Avery', status: 'Launch ready' },\n  { product: 'USB-C Dock', owner: 'Morgan', status: 'In review' },\n  { product: 'Mechanical Keyboard', owner: 'Riley', status: 'Backorder' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"owner\">\n        <DataTableColumnHeader>Owner</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-muted px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What the wrapper file owns\n\nThe wrapper covers _presentation_:\n\n- default row, header, sticky-column, empty, and loading components\n- Tailwind classes for cells, headers, and group headers\n- the default table frame (`rounded-md border`) and table surface variables\n- ergonomic names (`DataTable`, `DataTableColumn`, `DataTableColumnHeader`, `DataTableCell`)\n- re-exports of the headless hooks and helpers you'll use day-to-day\n\nEdit it like any other component in your app. Tailwind classes, default props, alternate variants, additional re-exports — all fair game. The wrapper file imports behavior from `@virtuoso.dev/data-table`, so your styling edits never fork table mechanics.\n\n## Table surface variables\n\nThe default shadcn wrapper gives `DataTable` an app-level frame (`rounded-md border`) and defines a small set of CSS variables on the same root. The frame border uses `--data-table-border`; callers do not need to add `rounded-md border` at each table instance. Use `className` only for intentional overrides such as `rounded-xl`, `border-0`, `border-2`, `shadow-sm`, or table variable overrides.\n\nThe variables matter most for sticky headers and sticky columns: those pieces are separate overlay containers, so they need an opaque background, but that background should match the table body instead of being hardcoded independently.\n\n| Variable                    | Default                        | Used for                                           |\n| --------------------------- | ------------------------------ | -------------------------------------------------- |\n| `--data-table-bg`           | `hsl(var(--background))`       | Table body, sticky header, sticky column wrappers  |\n| `--data-table-fg`           | `hsl(var(--foreground))`       | Default table/header/group-header text             |\n| `--data-table-border`       | `hsl(var(--border))`           | Row separators, header borders, loading containers |\n| `--data-table-muted`        | `hsl(var(--muted))`            | Group rows, skeleton rows, sticky hover blends     |\n| `--data-table-muted-fg`     | `hsl(var(--muted-foreground))` | Loading and secondary wrapper text                 |\n| `--data-table-row-hover`    | `hsl(var(--muted) / 0.5)`      | Non-sticky row hover background                    |\n| `--data-table-sticky-hover` | a mix of muted and table bg    | Sticky column hover background                     |\n\nIf your app maps shadcn tokens to a different design system, override these variables once on the `DataTable` root or in the copied wrapper defaults. Do not style sticky cells individually just to fix a surface mismatch.\n\n```tsx\n<DataTable\n  className=\"\n    [--data-table-bg:hsl(var(--surface-container-lowest))]\n    [--data-table-fg:hsl(var(--on-surface))]\n    [--data-table-border:hsl(var(--outline))]\n    [--data-table-muted:hsl(var(--surface-container-low))]\n    [--data-table-muted-fg:hsl(var(--on-surface-variant))]\n  \"\n  model={model}\n/>\n```\n\n## What the headless package owns\n\n`@virtuoso.dev/data-table` covers _behavior_:\n\n- virtualization, measurement, scrolling\n- `localModel()` and `remoteModel()`\n- the reactive engine (`engineRef`, `useRemoteCellValue`, `useRemotePublisher`)\n- opt-in feature modules (resize, reorder, visibility, persistence)\n\nBehavior imports always come from `@virtuoso.dev/data-table`. If you find yourself reaching into the headless package to change behavior, that's a signal you want an issue or a PR rather than a fork.\n\n## Feature-specific wrappers\n\nSome opt-in features ship their own wrapper components, following the same presentation/behavior split:\n\n- `data-table-resize-handle` — resize handle UI\n- `data-table-sort-header-button` — model-backed sort button UI\n- `data-table-reorder-grip`, `data-table-reorder-drop-zone`, `data-table-draggable-group-header` — reorder UI\n\nThese names are registry item names, not import paths; shadcn installs them under nested paths such as `@/components/ui/data-table/column-resize` and `@/components/ui/data-table/column-sort`. Each one is header-slot UI you can restyle. The streams they publish to live in the corresponding `@virtuoso.dev/data-table/...` subpaths — see [Header Slots](/data-table/customization/header-slots/) for how those slots compose, and the column feature pages for what each registry component does.\n\n## When you'll come back here\n\nThe wrapper is the right place to change:\n\n- default Tailwind classes on `DataTable`, `DataTableCell`, `DataTableColumnHeader`\n- the default table frame, radius, border, and surface variables\n- the default `Row`, `EmptyPlaceholder`, `LoadingPlaceholder` your team should see\n- ergonomic re-exports you add for your codebase\n\nFor replacing the table's structural components (Row, GroupHeader, sticky wrappers) on a per-instance basis without touching the wrapper file, see [Replacing Internals](/data-table/customization/replacing-internals/).\n","packages/data-table/docs/7.customization/08.card-mode.md":"---\ntitle: Card Mode\ndescription: Switch the same data model between table rows and an automatically measured grid of equal-sized cards.\nsidebar:\n  label: Card Mode\n---\n\nUsers can switch between a table and cards without losing their sorting or filtering. Your application owns the switch; the table keeps the same data model connected in either presentation.\n\n## Switch the presentation\n\nPass `mode=\"card\"` and a `components.Card` component to show cards. The default mode is `\"table\"`. Each switch starts at the first item; it does not restore the previous scroll position or repeat the model handshake.\n\n`Card` receives `{ data, index, context }` inside a wrapper that the library measures. The optional `CardHeader` receives `{ context }` and stays above the cards as a sticky header. Both are inside the same engine provider as the table, so header controls can read model action state and dispatch sorting or filtering actions.\n\nThis example uses the editable [shadcn wrapper](../shadcn-wrapper/) and keeps its column declarations mounted in both modes.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { dispatchModelAction$, localModel, modelActionState$, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport type { CardComponentProps } from '@virtuoso.dev/data-table'\n\ninterface Service {\n  id: number\n  name: string\n  requests: number\n}\n\nconst services: Service[] = Array.from({ length: 1000 }, (_, id) => ({\n  id,\n  name: `Service ${id}`,\n  requests: (id * 137) % 10000,\n}))\n\nfunction ServiceCard({ data }: CardComponentProps<Service>) {\n  return (\n    <article>\n      <h3 className=\"font-medium\">{data.name}</h3>\n      <p>{data.requests.toLocaleString()} requests</p>\n    </article>\n  )\n}\n\nfunction Controls() {\n  const dispatch = usePublisher(dispatchModelAction$)\n  const state = useCellValue(modelActionState$)\n  return (\n    <label className=\"flex items-center gap-2 bg-background p-3\">\n      <input\n        type=\"checkbox\"\n        checked={Boolean(state.sort?.payload)}\n        onChange={(event) => dispatch({ action: 'sort', payload: event.target.checked })}\n      />\n      Descending requests\n    </label>\n  )\n}\n\nconst components = { Card: ServiceCard, CardHeader: Controls }\n\nexport default function App() {\n  const [mode, setMode] = React.useState<'card' | 'table'>('card')\n  const model = React.useMemo(\n    () =>\n      localModel<Service>({\n        data: services,\n        pipeline: ['sort'],\n        actions: {\n          sort: {\n            stage: 'sort',\n            handler: ({ data, payload }: { data: Service[]; payload: unknown }) =>\n              payload ? data.toSorted((a, b) => b.requests - a.requests) : data,\n          },\n        },\n      }),\n    []\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <button className=\"rounded border px-3 py-1\" onClick={() => setMode(mode === 'card' ? 'table' : 'card')}>\n        Show {mode === 'card' ? 'table' : 'cards'}\n      </button>\n      <DataTable<Service, unknown, never>\n        model={model}\n        mode={mode}\n        components={components}\n        computeRowKey={({ data }) => data.id}\n        cardListClassName=\"grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-3\"\n        cardItemClassName=\"h-36 min-w-0 overflow-hidden rounded border p-4\"\n        style={{ height: 380 }}\n      >\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Service</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"requests\">\n          <DataTableColumnHeader>\n            <Controls />\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nFor filtering or remote sorting, connect controls to the same [model actions](../../data-model/) you use in table mode. Card mode does not generate operation controls or choose breakpoints.\n\n## Define a uniform CSS layout\n\nStyle the list with `cardListClassName` and its item wrappers with `cardItemClassName`. The renderer measures one representative wrapper, the container width, and the computed CSS gaps. It derives the number of columns and virtualizes whole lines. There is no numeric card-size prop.\n\n- Every card must have the same outer width and height at the current container width. Matching heights within each individual grid row is not sufficient.\n- Use a regular row-major CSS grid, such as `repeat(auto-fill, minmax(200px, 1fr))`, or a wrapping flex layout with equal-width items and `flex-shrink: 0`.\n- Use `gap`, `row-gap`, and `column-gap` with lengths that resolve to pixels for spacing. Percentage gaps, item margins, spanning items, dense placement, and space-distributed alignment are not supported.\n- Keep list padding and borders off the measured list. The library owns its vertical padding for virtualization. Put decoration on the card wrappers or outside the table.\n- CSS may change dimensions and gaps responsively. A resize observer remeasures the active layout, including after a hidden container becomes visible.\n- Remote placeholders must use the same dimensions as loaded cards. Supply a schema-compatible placeholder and handle it in `Card`.\n\nCard dimensions never enter the table row measurement cache, and row heights never seed card geometry. `itemHeight$` remains a **table-row** measurement API. Each presentation remeasures when activated; retained data and column settings do not imply retained geometry.\n\nUse the local Card Mode Ladle preview to change container width and card height independently, then switch to table rows of a different height.\n\n## Scrolling and loading\n\n`initialLocation`, `scrollToRow$`, and `scrollIntoView$` refer to **model item indexes**, not visual grid-row indexes. The same is true of `viewportRange$` and remote-model viewport requests. `increaseViewportBy` adds pixel overscan, and `onRenderedDataChange` reports the mounted model items.\n\nCards support internal scrolling, `useWindowScroll`, and `customScrollParent`. Their sticky header is included in the visible-height calculation. Empty, initial-loading, refresh-overlay, and append-footer slots use the same model loading state as the table. Switching during a request does not cancel or restart it; the new viewport can request additional data normally.\n\nSee [Scroll Containers](../scroll-containers/) for scroll-owner setup and [Empty and Loading States](../empty-and-loading-states/) for loading and retry controls. The Card Mode Ladle previews include remote offset and append models with delayed requests and simulated errors.\n\n## Limits and recovery\n\nCard mode requires `components.Card` and ungrouped model data. Otherwise it displays a configuration error. Providing the missing component, sending an ungrouped result, or returning to table mode recovers without replacing the model. It does not silently flatten groups or change your grouping action.\n\nColumn headers, column resizing, sticky columns, and column reordering are table-only surfaces. Their declarations and configuration remain available when the application switches back.\n\nUse table mode when users need grouped rows. For unequal card dimensions or masonry placement, use a dedicated layout such as [Virtuoso Masonry](../../../masonry/) instead of this uniform card presentation.\n","packages/data-table/docs/8.examples/01.basic-table.md":"---\ntitle: Basic Table Example\ndescription: Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.\nsidebar:\n  label: Basic Table\n---\n\nThree columns, a hardcoded product list, and the styled shadcn wrapper. The shape you'll arrive at on day one when you drop the table into a screen, before any backend wiring, sorting, or pinning enters the picture.\n\nThe purpose of this example is to fix that day-one shape. The `data` array is passed to `localModel`; each `<DataTableColumn>` declares a `field` and the cell renderer for that field. Nothing is lifted into React state — the table owns its own scroll, selection, and layout state through the engine, and the column declarations live alongside the markup that uses them. Every later example in this section adds one capability on top of this skeleton; if anything below ever feels too dense, this is the page to return to.\n\n## APIs used\n\n- `DataTable` for scrolling and virtualization\n- `DataTableColumn` for declarative columns\n- `DataTableColumnHeader` and `DataTableCell` for presentation\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## What this leaves out\n\n- Grouped rows — add `groups` markers and a `GroupHeaderCell`\n- Sticky pinning — add `sticky=\"left\"` or `sticky=\"right\"`\n- Remote data — swap `localModel` for `remoteModel`\n- Stable row identity — add `computeRowKey` once rows can sort, filter, or move\n","packages/data-table/docs/8.examples/03.grouped-data.md":"---\ntitle: Group Rows by Region and Category\ndescription: Organize a large inventory table into nested groups while preserving virtualization and sticky headers.\nsidebar:\n  label: Grouped Rows\n---\n\nThis example precomputes a flattened grouped result, then lets the table virtualize group headers and product rows together. Regions are top-level group rows, categories are second-level group rows, and products remain ordinary data rows.\n\n## APIs used\n\n- `localModel({ data, groups })` receives the flattened row stream and group markers.\n- `groups: [{ index, level }]` identifies which rows are group headers and how deeply they nest.\n- `GroupHeaderCell` renders the marked group rows instead of rendering column cells for them.\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { groupedProducts } from './data'\nimport type { GroupLabel, Product } from './data'\n\nconst model = localModel(groupedProducts)\n\nfunction rowKey(row: GroupLabel | Product) {\n  return 'id' in row ? row.id : row.groupKey\n}\n\nfunction productName(row: GroupLabel | Product) {\n  return 'name' in row ? row.name : ''\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => rowKey(data)} model={model} style={{ height: 420 }}>\n      <GroupHeaderCell>\n        {({ level, row }) => (\n          <div className={level === 0 ? 'bg-muted px-3 py-2 font-semibold' : 'bg-muted/50 px-6 py-2 font-medium'}>\n            {(row.data as { label: string }).label}\n          </div>\n        )}\n      </GroupHeaderCell>\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => productName(row.data)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"region\">\n        <DataTableColumnHeader>Region</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  region: 'US' | 'EU'\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface GroupLabel {\n  groupKey: string\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\nconst regions: Product['region'][] = ['US', 'EU']\n\nconst products: Product[] = Array.from({ length: 90 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    region: regions[index % regions.length]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport const groupedProducts: {\n  data: (GroupLabel | Product)[]\n  groups: { index: number; level: number }[]\n} = (() => {\n  const data: (GroupLabel | Product)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const region of regions) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ groupKey: `region-${region}`, label: `Region: ${region}` })\n\n    for (const category of categories) {\n      const rows = products.filter((product) => product.region === region && product.category === category)\n      groups.push({ index: data.length, level: 1 })\n      data.push({ groupKey: `region-${region}-category-${category}`, label: `${category} (${rows.length})` })\n      data.push(...rows)\n    }\n  }\n\n  return { data, groups }\n})()\n```\n\n## Notes\n\nGroup labels are virtualized rows, so deep nesting still scrolls cheaply. The practical limit is readability: two levels is usually the ceiling before labels need to be very short. If the grouping changes at runtime, keep stable keys on both product rows and group rows so React can preserve row identity as sections move.\n","packages/data-table/docs/8.examples/04.sticky-columns.md":"---\ntitle: Sticky Columns Example\ndescription: Pin identity and status columns so users can scan wide datasets without losing the row context.\nsidebar:\n  label: Sticky Columns\n---\n\nA product column pinned to the left, a status column pinned to the right, and a dozen metric columns in between to force horizontal scrolling — the only situation where sticky columns matter. The toolbar above the table flips the status column's sticky side at runtime through the engine.\n\n## APIs used\n\n- `sticky=\"left\"` / `sticky=\"right\"` — declarative pin\n- `setColumnSticky$` — runtime sticky changes from external UI\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, setColumnSticky$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport { metricFields, products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'right' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status right\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\" sticky=\"left\">\n          <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n\n        {metricFields.map((field) => (\n          <DataTableColumn field={field} key={field}>\n            <DataTableColumnHeader className=\"min-w-[140px]\">{field}</DataTableColumnHeader>\n            <DataTableCell className=\"tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        ))}\n\n        <DataTableColumn field=\"status\" sticky=\"right\">\n          <DataTableColumnHeader className=\"min-w-[120px]\">Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const metricFields = Array.from({ length: 12 }, (_, index) => `metric${index + 1}`)\n\nexport const products = Array.from({ length: 120 }, (_, rowIndex) => {\n  const stock = (rowIndex * 5) % 32\n  const base: Record<string, string | number> = {\n    id: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n    name: `Product ${rowIndex + 1}`,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n\n  for (const field of metricFields) {\n    base[field] = (rowIndex + 1) * 7 + field.charCodeAt(field.length - 1)\n  }\n\n  return base\n})\n```\n\n## Notes\n\nEach pinned column takes width from the scrollable area. Past two or three sticky columns, horizontal scrolling stops being useful — you've spent your row width on pinned content and the remaining viewport can no longer fit anything meaningful.\n\nThe runtime toggle uses `setColumnSticky$` against the same column key the table assigned during declaration. Look up the key by field with `columns$` once at the call site instead of holding it in component state; the engine is the source of truth.\n","packages/data-table/docs/8.examples/06.column-reorder.md":"---\ntitle: Column Reorder Example\ndescription: Reorder columns with the shadcn drag handle and drop zone components.\nsidebar:\n  label: Column Reorder\n---\n\nEach header carries a `GripVertical` icon at the start and a transparent drop zone across the rest. Drag a grip over another header to move the source column before or after the target.\n\nThe shadcn components import from `@virtuoso.dev/data-table/column-reorder` — consumers that never reorder columns don't ship that reducer. The grip is a Lucide icon, sized and spaced like the other shadcn header affordances so it visually separates from the column title.\n\n## APIs used\n\n- `ReorderGrip` — draggable header affordance\n- `ReorderDropZone` — header drop target\n- `HeaderStart` / `HeaderOverlay` — slot positions for the components\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 400 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nDrag-and-drop is the spreadsheet-style affordance. For accessible alternatives or deterministic test setup, publish `reorderColumns$` from an external menu or button — the streams accept the same operations the drag UI emits.\n","packages/data-table/docs/8.examples/07.local-filter-sort-group.md":"---\ntitle: Local Filter, Sort, Group\ndescription: Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.\nsidebar:\n  label: Local Filter, Sort, Group\n---\n\nA filter dropdown, a sort dropdown, and a grouping dropdown share one `localModel` pipeline. The pipeline runs filter → sort → group; each control publishes one action and only the affected stage (and the stages after it) rerun.\n\n## APIs used\n\n- `localModel()`\n- `pipeline` to declare transformation order\n- `stage` to attach an action to a pipeline step\n- `model.send()` to publish filter, sort, and group changes\n- `GroupHeaderCell`\n\n```tsx live wide file=App.tsx\nimport { useEffect, useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { FilterBar } from './FilterBar'\nimport { groupProducts, products } from './data'\n\nimport type { Product, ProductGroup } from './data'\n\nfunction isProduct(item: Product | ProductGroup): item is Product {\n  return 'id' in item\n}\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n  const [sortBy, setSortBy] = useState<'name' | 'price' | 'revenue'>('name')\n  const [groupBy, setGroupBy] = useState<'none' | 'category' | 'status'>('category')\n\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['filter', 'sort', 'group'],\n      actions: {\n        filter: {\n          stage: 'filter',\n          handler: ({ data, payload }) => {\n            const nextCategory = payload as string\n            const rows = data.filter(isProduct)\n            return nextCategory === 'all' ? rows : rows.filter((product) => product.category === nextCategory)\n          },\n        },\n        sort: {\n          stage: 'sort',\n          handler: ({ data, payload }) => {\n            const field = payload as 'name' | 'price' | 'revenue'\n            return data.filter(isProduct).toSorted((a, b) => (a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0))\n          },\n        },\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as 'none' | 'category' | 'status'),\n        },\n      },\n    })\n  )\n\n  useEffect(() => {\n    model.send({ action: 'filter', payload: category })\n  }, [category, model])\n\n  useEffect(() => {\n    model.send({ action: 'sort', payload: sortBy })\n  }, [sortBy, model])\n\n  useEffect(() => {\n    model.send({ action: 'group', payload: groupBy })\n  }, [groupBy, model])\n\n  return (\n    <div className=\"space-y-4\">\n      <FilterBar\n        category={category}\n        groupBy={groupBy}\n        onCategoryChange={setCategory}\n        onGroupByChange={setGroupBy}\n        onSortByChange={setSortBy}\n        sortBy={sortBy}\n      />\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <GroupHeaderCell className=\"bg-muted px-3 py-2 font-medium\">{({ row }) => row.data.label}</GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=FilterBar.tsx\ninterface FilterBarProps {\n  category: string\n  groupBy: 'none' | 'category' | 'status'\n  onCategoryChange: (value: string) => void\n  onGroupByChange: (value: 'none' | 'category' | 'status') => void\n  onSortByChange: (value: 'name' | 'price' | 'revenue') => void\n  sortBy: 'name' | 'price' | 'revenue'\n}\n\nexport function FilterBar({ category, groupBy, onCategoryChange, onGroupByChange, onSortByChange, sortBy }: FilterBarProps) {\n  return (\n    <div className=\"flex flex-wrap gap-3\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onCategoryChange(event.target.value)}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onSortByChange(event.target.value as 'name' | 'price' | 'revenue')}\n        value={sortBy}\n      >\n        <option value=\"name\">Sort by name</option>\n        <option value=\"price\">Sort by price</option>\n        <option value=\"revenue\">Sort by revenue</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onGroupByChange(event.target.value as 'none' | 'category' | 'status')}\n        value={groupBy}\n      >\n        <option value=\"none\">No groups</option>\n        <option value=\"category\">Group by category</option>\n        <option value=\"status\">Group by status</option>\n      </select>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n  revenue: number\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface ProductGroup {\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nexport const products: Product[] = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    price: 49 + (index % 11) * 13,\n    revenue: 1_100 + index * 85,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport function groupProducts(data: Product[], groupBy: 'none' | 'category' | 'status') {\n  if (groupBy === 'none') {\n    return data\n  }\n\n  const groups = new Map<string, Product[]>()\n\n  for (const item of data) {\n    const key = groupBy === 'category' ? item.category : item.status\n    const bucket = groups.get(key)\n    if (bucket) {\n      bucket.push(item)\n    } else {\n      groups.set(key, [item])\n    }\n  }\n\n  const flatData: (Product | ProductGroup)[] = []\n  const markers: { index: number; level: number }[] = []\n\n  for (const [key, items] of groups) {\n    markers.push({ index: flatData.length, level: 0 })\n    flatData.push({ label: `${key} (${items.length})` })\n    flatData.push(...items)\n  }\n\n  return { data: flatData, groups: markers }\n}\n```\n\n## When to outgrow this shape\n\n- The full dataset doesn't fit in memory — switch to `remoteModel` and move filter/sort/group into request params.\n- Filtering rules need server-side evaluation (permissions, complex SQL, full-text search) — keep the table local for display but fetch filtered rows.\n","packages/data-table/docs/8.examples/08.remote-offset.md":"---\ntitle: Server-side Paging\ndescription: Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.\nsidebar:\n  label: Paging\n---\n\nOffset mode requests only the pages near the viewport. Placeholders fill the rest of the scroll height while pages are pending, so total scroll size stays correct from the first render.\n\nThe fetch function receives `{ offset, limit, params, signal }` and returns `{ rows, totalCount }`.\n\n## APIs used\n\n- `remoteModel()`\n- `defaultOffsetViewportHandler` — built-in handler for offset/limit backends\n- `AbortSignal`-aware fetch functions\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          handler: ({ params, payload }) => ({ ...params, category: payload as string }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => {\n          const nextCategory = event.target.value\n          setCategory(nextCategory)\n          model.send({ action: 'filter', payload: nextCategory })\n        }}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(220, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nOffset mode needs the backend to answer count-aware slice queries efficiently. For cursor APIs, search-after pagination, or feeds that don't expose a total count, see the [Cursor Load More example](/data-table/examples/remote-append/).\n","packages/data-table/docs/8.examples/09.remote-append.md":"---\ntitle: Endless Scrolling\ndescription: Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.\nsidebar:\n  label: Endless Scrolling\n---\n\nEndless scrolling — append mode — accumulates rows as the user reaches the end of the loaded list. New pages are concatenated onto what's already loaded; there is no total count and no jump-to-row. The fetch function receives `{ cursor, limit, params, signal }` and returns `{ rows, cursor, hasMore }`.\n\n## APIs used\n\n- `mode: 'append'` — turns on append-mode bookkeeping\n- `defaultAppendViewportHandler` — built-in viewport handler that triggers `loadMore` near the end\n- `model.send({ action: 'loadMore' })` — manual load-more trigger\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultAppendViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts } from './api'\n\nimport type { Product } from './api'\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product>({\n      fetch: fetchProducts,\n      initialParams: {},\n      mode: 'append',\n      onViewportChange: defaultAppendViewportHandler,\n      pageSize: 20,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <Button onClick={() => model.send({ action: 'loadMore' })} variant=\"outline\">\n        Load more\n      </Button>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { AppendFetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 180 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n}))\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: AppendFetchParams<Record<string, never>>) {\n  await pause(220, params.signal)\n  const start = (params.cursor as number | undefined) ?? 0\n  const rows = allProducts.slice(start, start + params.limit)\n  const nextCursor = start + rows.length\n\n  return {\n    cursor: nextCursor,\n    hasMore: nextCursor < allProducts.length,\n    rows,\n  }\n}\n```\n\n## When this doesn't fit\n\nUsers can't jump to row 50,000 without loading everything before it. For known total counts and arbitrary positioning, see the [Server-side Pages example](/data-table/examples/remote-offset/).\n","packages/data-table/docs/8.examples/10.table-instance-dashboard.md":"---\ntitle: External Toolbar\ndescription: Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.\nsidebar:\n  label: External Toolbar\n---\n\nA control panel beside the table reads `scrollLocation$` and publishes `scrollToRow$` and `setColumnSticky$` through a shared `engineRef`. Virtualization and feature state stay inside the table engine; React state stays out of it.\n\n## APIs used\n\n- `useEngineRef()`\n- `useRemoteCellValue()`\n- `useRemotePublisher()`\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, useEngineRef } from '@virtuoso.dev/data-table'\n\nimport { ControlPanel } from './ControlPanel'\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n\n  return (\n    <div className=\"grid gap-4 lg:grid-cols-[280px_minmax(0,1fr)]\">\n      <ControlPanel engineRef={engineRef} />\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=ControlPanel.tsx\nimport { Button } from '@/components/ui/button'\nimport { columns$, scrollLocation$, scrollToRow$, setColumnSticky$, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport type { ColumnInfo, EngineRef } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nexport function ControlPanel({ engineRef }: { engineRef: EngineRef }) {\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4 rounded-xl border bg-muted/30 p-4 text-sm\">\n      <div className=\"space-y-1\">\n        <p className=\"font-semibold\">Table controls</p>\n        <p className=\"text-muted-foreground\">\n          {scrollLocation === undefined ? 'Waiting for table state…' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n        </p>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 79, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 80\n        </Button>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nThe cells and actions you publish to (`scrollToRow$`, `setColumnSticky$`, `scrollLocation$`, etc.) are the public API surface for this table — pin their names in tests and treat renames the same way you'd treat a prop rename.\n","packages/data-table/docs/8.examples/11.scrollbar-overlay.md":"---\ntitle: Keep Sticky Columns Clear of Overlay Scrollbars\ndescription: Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.\nsidebar:\n  label: Overlay Scrollbars\n---\n\nmacOS overlay scrollbars sit on top of content and can cover the last sticky-right column. The header and cell render params expose `overlaidByScrollbar` — true when an overlay scrollbar covers this cell — so you can add compensating right padding only when needed.\n\n## APIs used\n\n- `overlaidByScrollbar` — render-param flag in cell and header renderers\n- `sticky=\"right\"` — column pin where the bug shows up\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { columns, rows } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 400 }}>\n      <DataTableColumn field=\"sku\" sticky=\"left\">\n        <DataTableColumnHeader>SKU</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      {columns.map((column) => (\n        <DataTableColumn field={column} key={column}>\n          <DataTableColumnHeader className=\"min-w-[130px]\">{column}</DataTableColumnHeader>\n          <DataTableCell>{({ row }) => String(row.data[column])}</DataTableCell>\n        </DataTableColumn>\n      ))}\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[150px] justify-end\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Actions\n            </span>\n          )}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right text-sm text-muted-foreground\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Edit · Duplicate\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const columns = Array.from({ length: 16 }, (_, index) => `metric${index + 1}`)\n\nexport const rows = Array.from({ length: 120 }, (_, rowIndex) => {\n  const row: Record<string, string> = {\n    sku: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n  }\n\n  for (const column of columns) {\n    row[column] = `R${rowIndex + 1}-${column}`\n  }\n\n  return row\n})\n```\n\n## Notes\n\nOnly sticky-right columns need this. The bug is platform-specific (macOS, sometimes iPadOS) — easy to miss in development on other platforms, so bake the padding in early.\n\nColumn widths come from header measurements. Set `min-w-*` on the header only; never on `DataTableCell`. A width class on the cell competes with the engine's column-width sync and the cells start overlapping each other when the table re-measures.\n","packages/data-table/docs/8.examples/12.custom-scroll-container.md":"---\ntitle: Embed a Table in a Shared Scroll Area\ndescription: Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.\nsidebar:\n  label: Shared Scroll Area\n---\n\nThe table renders inside a parent with its own `overflow: auto`, so notes, filters, and the table body all scroll together. `customScrollParent` tells the table to drive virtualization from that outer element instead of its own scroller.\n\n## APIs used\n\n- `customScrollParent` — attaches the table to an external scrolling element\n- An outer container with `overflow: auto` and a real height\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null)\n\n  return (\n    <div className=\"rounded-xl\">\n      <div className=\"border-b px-4 py-3\">\n        <p className=\"font-semibold\">Shared scroll surface</p>\n        <p className=\"text-sm text-muted-foreground\">The note above the table scrolls together with the rows.</p>\n      </div>\n\n      <div className=\"h-[420px] overflow-auto\" ref={setScrollParent}>\n        <div className=\"border-b px-4 py-3 text-sm text-muted-foreground\">\n          Filters, charts, or help text can live in the same scrolling context as the table body.\n        </div>\n\n        <DataTable className=\"min-w-[720px]\" customScrollParent={scrollParent} model={model}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Product</DataTableColumnHeader>\n            <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"category\">\n            <DataTableColumnHeader>Category</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"region\">\n            <DataTableColumnHeader>Region</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"status\">\n            <DataTableColumnHeader>Status</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 160 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n```\n\n## Notes\n\nThe outer container's height bounds the entire layout, including the table. Sticky table headers now interact with sibling sticky elements inside the same scroll context — test both together.\n\nDo not set `style={{ height }}` on the table when `customScrollParent` is in use. The table grows to its virtualized content height; the outer container's scroll height drives virtualization. A fixed table height clips the rows before the scroll parent can reach them, and the bottom rows appear to never load.\n","packages/data-table/docs/8.examples/13.column-resizing.md":"---\ntitle: Column Resizing Example\ndescription: Add resize handles and external width controls to a table.\nsidebar:\n  label: Column Resizing\n---\n\nA resize handle on every column plus an external button that sets the product column to a fixed width. Both publish to the same `resizeColumn$` stream; `columnWidths$` exposes the realized layout for the readout.\n\n## APIs used\n\n- `HeaderEdge` — slot position for the handle\n- `ResizeHandle` — shadcn drag affordance\n- `resizeColumn$` — width override stream\n- `columnWidths$` — realized layout state for display\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel, columnWidths$, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnWidthOverrides$, resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nimport { products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const widths = useRemoteCellValue(columnWidths$, engineRef)\n  const resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n  const resetColumnWidthOverrides = useRemotePublisher(resetColumnWidthOverrides$, engineRef)\n\n  const nameKey = findColumnKey(columns, 'name')\n  const nameWidth = nameKey && widths ? Math.round(widths.get(nameKey) ?? 0) : 0\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n        <Button\n          disabled={!nameKey}\n          onClick={() => {\n            if (nameKey) {\n              resizeColumn({ key: nameKey, width: 280 })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Set product to 280px\n        </Button>\n        <Button onClick={() => resetColumnWidthOverrides()} variant=\"outline\">\n          Reset widths\n        </Button>\n        <span className=\"text-muted-foreground\">Product width: {nameWidth || 'measuring'}px</span>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Stock'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n```\n\n## Notes\n\nDrag handles cover spreadsheet-style interaction. External controls cover presets, reset buttons, and commands that shouldn't depend on a pointer — both publish to the same stream, so they stay consistent.\n","packages/data-table/docs/8.examples/14.column-visibility.md":"---\ntitle: Column Visibility Example\ndescription: Build a toolbar that hides and restores columns at runtime.\nsidebar:\n  label: Column Visibility\n---\n\nA toolbar reads `columns$` and `columnVisibilityState$` through an `engineRef` and publishes `setColumnVisibility$` and `resetColumnVisibility$`. The `internalId` column starts hidden via `visible={false}` and can be toggled back on from the toolbar.\n\n## APIs used\n\n- `columns$` — column declarations from the engine\n- `columnVisibilityState$` — current visibility overrides\n- `setColumnVisibility$` / `resetColumnVisibility$` — toggle and reset\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n          return (\n            <Button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} variant={visible ? 'secondary' : 'outline'}>\n              {visible ? 'Hide' : 'Show'} {column.field}\n            </Button>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    internalId: `inv_${1000 + index}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nPersisted visibility is keyed by `field`, not by the generated runtime column key. Renaming a field invalidates its saved visibility — see [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the migration rule.\n","packages/data-table/docs/8.examples/15.state-persistence.md":"---\ntitle: State Persistence Example\ndescription: Persist a remote filter action and column widths for a remote table.\nsidebar:\n  label: State Persistence\n---\n\nA remote category filter and the column widths both survive a reload. The model action opts into persistence with `persistence: true`; the column-width adapter saves resize overrides by field name. Both restore on mount through `DataTableStatePersistence`.\n\n## APIs used\n\n- `DataTableStatePersistence` — mounts the adapters under one storage key\n- `modelStatePersistenceAdapter()` — persists model action state\n- `columnWidthPersistenceAdapter()` — persists column width overrides\n- `persistence: true` — opt-in flag on a model action\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst adapters = [modelStatePersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          persistence: true,\n          handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  function applyCategory(nextCategory: string) {\n    model.send({ action: 'filter', payload: nextCategory })\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {['all', 'Office', 'Peripherals', 'Audio'].map((value) => (\n          <Button key={value} onClick={() => applyCategory(value)} variant=\"outline\">\n            {value}\n          </Button>\n        ))}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-example\" />\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Price'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## Notes\n\nRestore happens after columns and the model register with the engine, so a brief unpersisted render is normal on mount. Keep the `adapters` array stable (define outside the component or wrap in `useMemo`), and bump `storageKey` or `resetKey` when saved state should no longer apply.\n","packages/data-table/docs/8.examples/16.remote-column-sorting.md":"---\ntitle: Remote Column Sorting\ndescription: Use shadcn header buttons to send sort params to a remote data model.\nsidebar:\n  label: Remote Sorting\n---\n\nEach sortable column header sends a `sort` action to the remote model. The action updates the request params, the model refetches from the first page, and the active header button reads the current sort from `modelActionState$`. Do not keep a second `sortBy` / `sortOrder` React state just to paint the header; seed the model with `initialActions` for a default sort and let `modelActionState$` be the source of truth.\n\nThe shadcn `SortHeaderButton` defaults to a payload shaped as `{ field, direction }`, where `direction` cycles through `'asc'`, `'desc'`, and no sort.\n\nInstall the optional sort button wrapper before importing it:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-sort`.\n\n## APIs used\n\n- `remoteModel()` — refetches rows when request params change\n- `SortHeaderButton` — shadcn slot component for column-header sorting\n- `HeaderEnd` — places the sort button after the header label\n- `computeRowKey` — keeps row identity stable when sorting reorders rows\n- `initialActions` — seeds the active sort in model action state\n- `modelStatePersistenceAdapter()` — optionally persists the sort action payload across reloads\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEnd } from '@/components/ui/data-table'\nimport { SortHeaderButton } from '@/components/ui/data-table/column-sort'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst defaultSort = { field: 'name', direction: 'asc' } satisfies NonNullable<Query['sort']>\nconst persistenceAdapters = [modelStatePersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        sort: {\n          persistence: true,\n          strategy: 'supersede',\n          handler: ({ params, payload }) => ({ ...params, sort: payload as Query['sort'] }),\n        },\n      },\n      fetch: fetchProducts,\n      initialActions: [{ action: 'sort', payload: defaultSort }],\n      initialParams: { sort: defaultSort },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 420 }}>\n      <DataTableStatePersistence adapters={persistenceAdapters} storageKey=\"remote-column-sorting-example\" />\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Price'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => `$${cellValue}`}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  sort?: {\n    field: 'name' | 'category' | 'price'\n    direction: 'asc' | 'desc'\n  }\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nfunction compareProducts(sort: NonNullable<Query['sort']>) {\n  return (left: Product, right: Product) => {\n    const leftValue = left[sort.field]\n    const rightValue = right[sort.field]\n    const result =\n      typeof leftValue === 'number' && typeof rightValue === 'number'\n        ? leftValue - rightValue\n        : String(leftValue).localeCompare(String(rightValue), undefined, { numeric: true })\n\n    return sort.direction === 'asc' ? result : -result\n  }\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.sort ? allProducts.toSorted(compareProducts(params.params.sort)) : allProducts\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nIf your API uses a different query shape, render the button manually in `HeaderEnd` and pass `action`, `field`, `getDirection`, or `getPayload` props to `SortHeaderButton`. Copy the installed component into your app-specific table wrapper when every table in the app should share the same sort payload shape.\n","packages/data-table/docs/9.guides/01.testing.md":"---\ntitle: Testing\ndescription: Make table behavior deterministic in tests by providing explicit test dimensions.\nsidebar:\n  label: Testing\n---\n\n`VirtuosoDataTableTestingContext` hands the table fixed `itemHeight` and `viewportHeight` values so virtualization decisions don't depend on real layout. Use it in component tests that assert visible rows, loaded ranges, or loading/empty state.\n\nIn browser tests (Playwright, real layout), prefer the actual measurements — only reach for the testing context when layout is missing or timing-dependent.\n\n## Fixed-size test table\n\n```tsx live\nimport { localModel, VirtuosoDataTableTestingContext } from '@virtuoso.dev/data-table'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nconst rows = Array.from({ length: 20 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 240 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n`itemHeight` is the row height the table assumes; `viewportHeight` is the visible vertical space. In this example, 240px ÷ 40px = six rows before overscan.\n\nThe table still needs a real height in its own layout. Match the provider dimensions and the table height unless the test specifically targets a mismatch.\n\n## What to assert\n\nAssert behavior, not exact DOM node counts — overscan, sticky headers, and measurement buffers can render extra rows.\n\nGood browser-test assertions:\n\n- a row near the top is visible before scrolling\n- a target row becomes visible after `scrollToRow$` or user scrolling\n- an empty placeholder is visible when the model has no rows\n- a remote loading overlay appears while a request is pending\n\nFor component tests, wrap the table once in a shared helper:\n\n```tsx\nfunction renderTestTable(children: React.ReactNode) {\n  return render(\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      {children}\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n## Browser vs. component tests\n\n- Sticky columns, resize handles, drag interactions, custom scroll parents — test in the browser with real layout.\n- Visible-row and loading-state assertions — use `VirtuosoDataTableTestingContext` in component tests.\n- Keep data generation simple. Behavior is what you're validating, not visual polish.\n","packages/data-table/docs/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F02.performance.md -->","packages/data-table/docs/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F03.troubleshooting.md -->","packages/data-table/docs/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","packages/data-table/docs/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F05.debug-instrumentation.md -->","packages/data-table/src/features/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fsrc%2Ffeatures%2FREADME.md -->","packages/gurx/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fgurx%2FREADME.md -->","packages/masonry/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmasonry%2FREADME.md -->","packages/message-list/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2FREADME.md -->","packages/message-list/docs/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F10.smooth-scrolling.md -->","packages/message-list/docs/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F11.testing.md -->","packages/message-list/docs/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F15.imperative-data-api.md -->","packages/message-list/docs/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F19.resize-observer-errors.md -->","packages/message-list/docs/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F01.intro.md -->","packages/message-list/docs/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F02.message-list.md -->","packages/message-list/docs/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F03.loading-older-messages.md -->","packages/message-list/docs/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","packages/message-list/docs/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F05.receive-messages.md -->","packages/message-list/docs/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F06.send-messages.md -->","packages/message-list/docs/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F07.multiple-channels.md -->","packages/message-list/docs/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F20.scroll-modifier.md -->","packages/message-list/docs/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F21.controlled-mode-recipes.md -->","packages/message-list/docs/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F01.messaging.md -->","packages/message-list/docs/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F02.ai-chatbot.md -->","packages/message-list/docs/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F03.gemini.md -->","packages/message-list/docs/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F04.reactions.md -->","packages/message-list/docs/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F05.date-separators.md -->","packages/message-list/docs/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F06.scroll-to-reply.md -->","packages/message-list/docs/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F07.grouped-messages.md -->","packages/message-list/docs/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F4.headers-footers.md -->","packages/message-list/docs/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F5.context.md -->","packages/message-list/docs/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F6.item-keys.md -->","packages/message-list/docs/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F7.hooks.md -->","packages/message-list/docs/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F8.custom-scrollbar.md -->","packages/message-list/docs/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F80.licensing.md -->","packages/message-list/docs/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F9.scrolling-to-item.md -->","packages/react-virtuoso/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2FREADME.md -->","packages/react-virtuoso/docs/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fauto-resizing.md -->","packages/react-virtuoso/docs/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fbasic-usage.md -->","packages/react-virtuoso/docs/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustom-scroll-container.md -->","packages/react-virtuoso/docs/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustomize-rendering.md -->","packages/react-virtuoso/docs/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fendless-scrolling.md -->","packages/react-virtuoso/docs/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ffooter.md -->","packages/react-virtuoso/docs/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fhorizontal-mode.md -->","packages/react-virtuoso/docs/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Finitial-index.md -->","packages/react-virtuoso/docs/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fkeyboard-navigation.md -->","packages/react-virtuoso/docs/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fpress-to-load-more.md -->","packages/react-virtuoso/docs/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Frange-change-callback.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-handling.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","packages/react-virtuoso/docs/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-to-index.md -->","packages/react-virtuoso/docs/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftailwind-test.md -->","packages/react-virtuoso/docs/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftop-items.md -->","packages/react-virtuoso/docs/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fwindow-scrolling.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","packages/react-virtuoso/docs/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","packages/react-virtuoso/docs/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","packages/react-virtuoso/docs/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Fbasic-table.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","packages/react-virtuoso/docs/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-grouped.md -->","packages/react-virtuoso/docs/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","packages/react-virtuoso/docs/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmocking-in-tests.md -->","packages/react-virtuoso/docs/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","packages/react-virtuoso/docs/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Ftanstack-table-integration.md -->","packages/react-virtuoso/docs/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F6.troubleshooting.md -->","packages/reactive-engine-core/.claude/settings.local.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2F.claude%2Fsettings.local.json -->","packages/reactive-engine-core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2FREADME.md -->","packages/reactive-engine-core/docs/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F01.core-concepts.md -->","packages/reactive-engine-core/docs/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F02.engine-and-lifecycle.md -->","packages/reactive-engine-core/docs/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F03.transactions.md -->","packages/reactive-engine-core/docs/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F04.operators-and-combinators.md -->","packages/reactive-engine-core/docs/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F05.building-on-the-engine.md -->","packages/reactive-engine-core/docs/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F06.diagnostics.md -->","packages/reactive-engine-query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2FREADME.md -->","packages/reactive-engine-query/docs/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2Fdocs%2F01.queries-and-mutations.md -->","packages/reactive-engine-react/.claude/settings.local.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2F.claude%2Fsettings.local.json -->","packages/reactive-engine-react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2FREADME.md -->","packages/reactive-engine-react/docs/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2Fdocs%2F01.react-integration.md -->","packages/reactive-engine-router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2FREADME.md -->","packages/reactive-engine-router/docs/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2Fdocs%2F01.routing.md -->","packages/reactive-engine-storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2FREADME.md -->","packages/reactive-engine-storage/docs/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2Fdocs%2F01.storage-links.md -->","packages/virtuoso-skills/.claude-plugin/plugin.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2F.claude-plugin%2Fplugin.json -->","packages/virtuoso-skills/CHANGELOG.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FCHANGELOG.md -->","packages/virtuoso-skills/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FREADME.md -->","packages/virtuoso-skills/package.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fpackage.json -->","packages/virtuoso-skills/skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","packages/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","packages/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","packages/virtuoso-skills/skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","packages/virtuoso-skills/skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","packages/virtuoso-skills/skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","packages/virtuoso-skills/skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","packages/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","packages/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","packages/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","packages/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","packages/virtuoso-skills/skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","packages/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","packages/virtuoso-skills/skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","packages/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","packages/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","packages/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","packages/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","packages/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","packages/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","packages/virtuoso-skills/skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","packages/virtuoso-skills/skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","packages/virtuoso-skills/skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","packages/virtuoso-skills/skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","packages/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","packages/virtuoso-skills/skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","packages/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","packages/virtuoso-skills/skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","packages/virtuoso-skills/skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","packages/virtuoso-skills/skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","packages/virtuoso-skills/skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","packages/virtuoso-skills/skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","packages/virtuoso-skills/skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","packages/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","packages/virtuoso-skills/skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","packages/virtuoso-skills/tsconfig.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Ftsconfig.json -->","plugins/virtuoso-skills/.codex-plugin/plugin.json":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2F.codex-plugin%2Fplugin.json -->","plugins/virtuoso-skills/skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","plugins/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","plugins/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","plugins/virtuoso-skills/skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","plugins/virtuoso-skills/skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","plugins/virtuoso-skills/skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","plugins/virtuoso-skills/skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","plugins/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","plugins/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","plugins/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","plugins/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","plugins/virtuoso-skills/skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","plugins/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","plugins/virtuoso-skills/skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","plugins/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","plugins/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","plugins/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","plugins/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","plugins/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","plugins/virtuoso-skills/skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","plugins/virtuoso-skills/skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","plugins/virtuoso-skills/skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","plugins/virtuoso-skills/skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","plugins/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","plugins/virtuoso-skills/skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","plugins/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","plugins/virtuoso-skills/skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","plugins/virtuoso-skills/skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","plugins/virtuoso-skills/skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","skills/data-table/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2FSKILL.md -->","skills/data-table/references/1.installation/01.shadcn.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","skills/data-table/references/1.installation/02.headless.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","skills/data-table/references/2.data-model/01.local-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","skills/data-table/references/2.data-model/02.remote-data-model.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","skills/data-table/references/2.data-model/03.row-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","skills/data-table/references/2.data-model/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","skills/data-table/references/3.columns/01.defining-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","skills/data-table/references/3.columns/02.cell-and-header-renderers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","skills/data-table/references/3.columns/03.column-groups.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","skills/data-table/references/3.columns/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","skills/data-table/references/3.columns/05.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","skills/data-table/references/3.columns/06.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","skills/data-table/references/3.columns/07.column-reordering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","skills/data-table/references/3.columns/08.runtime-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","skills/data-table/references/3.columns/09.column-layout.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","skills/data-table/references/3.columns/index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","skills/data-table/references/4.grouped-rows.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","skills/data-table/references/5.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","skills/data-table/references/6.controlling-the-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","skills/data-table/references/7.customization/01.styling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","skills/data-table/references/7.customization/02.replacing-internals.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","skills/data-table/references/7.customization/03.empty-and-loading-states.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","skills/data-table/references/7.customization/04.scroll-containers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","skills/data-table/references/7.customization/05.ambient-context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","skills/data-table/references/7.customization/06.header-slots.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","skills/data-table/references/7.customization/07.shadcn-wrapper.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","skills/data-table/references/7.customization/08.card-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","skills/data-table/references/8.examples/01.basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","skills/data-table/references/8.examples/03.grouped-data.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","skills/data-table/references/8.examples/04.sticky-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","skills/data-table/references/8.examples/06.column-reorder.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","skills/data-table/references/8.examples/07.local-filter-sort-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","skills/data-table/references/8.examples/08.remote-offset.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","skills/data-table/references/8.examples/09.remote-append.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","skills/data-table/references/8.examples/10.table-instance-dashboard.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","skills/data-table/references/8.examples/11.scrollbar-overlay.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","skills/data-table/references/8.examples/12.custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","skills/data-table/references/8.examples/13.column-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","skills/data-table/references/8.examples/14.column-visibility.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","skills/data-table/references/8.examples/15.state-persistence.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","skills/data-table/references/8.examples/16.remote-column-sorting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","skills/data-table/references/9.guides/01.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","skills/data-table/references/9.guides/02.performance.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","skills/data-table/references/9.guides/03.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","skills/data-table/references/9.guides/05.debug-instrumentation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","skills/data-table/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2FREADME.md -->","skills/message-list/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2FSKILL.md -->","skills/message-list/references/10.smooth-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","skills/message-list/references/11.testing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F11.testing.md -->","skills/message-list/references/15.imperative-data-api.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","skills/message-list/references/19.resize-observer-errors.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","skills/message-list/references/2.tutorial/01.intro.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","skills/message-list/references/2.tutorial/02.message-list.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","skills/message-list/references/2.tutorial/03.loading-older-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","skills/message-list/references/2.tutorial/05.receive-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","skills/message-list/references/2.tutorial/06.send-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","skills/message-list/references/2.tutorial/07.multiple-channels.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","skills/message-list/references/20.scroll-modifier.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","skills/message-list/references/21.controlled-mode-recipes.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","skills/message-list/references/3.examples/01.messaging.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","skills/message-list/references/3.examples/02.ai-chatbot.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","skills/message-list/references/3.examples/03.gemini.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","skills/message-list/references/3.examples/04.reactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","skills/message-list/references/3.examples/05.date-separators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","skills/message-list/references/3.examples/06.scroll-to-reply.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","skills/message-list/references/3.examples/07.grouped-messages.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","skills/message-list/references/4.headers-footers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","skills/message-list/references/5.context.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F5.context.md -->","skills/message-list/references/6.item-keys.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","skills/message-list/references/7.hooks.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F7.hooks.md -->","skills/message-list/references/8.custom-scrollbar.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","skills/message-list/references/80.licensing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F80.licensing.md -->","skills/message-list/references/9.scrolling-to-item.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","skills/message-list/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2FREADME.md -->","skills/react-virtuoso/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2FSKILL.md -->","skills/react-virtuoso/references/1.virtuoso/auto-resizing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","skills/react-virtuoso/references/1.virtuoso/basic-usage.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","skills/react-virtuoso/references/1.virtuoso/customize-rendering.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","skills/react-virtuoso/references/1.virtuoso/footer.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","skills/react-virtuoso/references/1.virtuoso/initial-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","skills/react-virtuoso/references/1.virtuoso/range-change-callback.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-handling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","skills/react-virtuoso/references/1.virtuoso/tailwind-test.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","skills/react-virtuoso/references/1.virtuoso/top-items.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","skills/react-virtuoso/references/1.virtuoso/window-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","skills/react-virtuoso/references/4.table-virtuoso/basic-table.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","skills/react-virtuoso/references/6.troubleshooting.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","skills/react-virtuoso/references/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2FREADME.md -->","skills/reactive-engine/SKILL.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2FSKILL.md -->","skills/reactive-engine/references/core/01.core-concepts.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","skills/reactive-engine/references/core/02.engine-and-lifecycle.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","skills/reactive-engine/references/core/03.transactions.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","skills/reactive-engine/references/core/04.operators-and-combinators.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","skills/reactive-engine/references/core/05.building-on-the-engine.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","skills/reactive-engine/references/core/06.diagnostics.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","skills/reactive-engine/references/core/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","skills/reactive-engine/references/query/01.queries-and-mutations.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","skills/reactive-engine/references/query/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","skills/reactive-engine/references/react/01.react-integration.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","skills/reactive-engine/references/react/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","skills/reactive-engine/references/router/01.routing.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","skills/reactive-engine/references/router/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","skills/reactive-engine/references/storage/01.storage-links.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","skills/reactive-engine/references/storage/README.md":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nRepo-specific guidance for agents working in `react-virtuoso`.\n\nUse this file for repo-specific navigation, validation, and documentation rules only. Keep it short, concrete, and limited to information that helps an agent make better decisions in this repository.\n\nIf a rule here appears to conflict with `package.json`, the actual filesystem, or a more specific instruction file in scope, trust the repo state and the more specific instruction.\n\n## High-Signal Rules\n\n- This is a `pnpm` workspaces monorepo. Prefer running commands from the repo root unless you need package-local scope.\n- Root `pnpm lint` does **not** run type checking. Run `pnpm typecheck` separately when you need type validation.\n- Internal working artifacts belong in Notion, not in repo markdown files.\n  - Use Notion for PRPs, plans, clarifications, execution notes, verification notes, prompts, investigations, reports, audits, and exported findings that are not product docs.\n  - Do not create or keep repo markdown artifacts in folders like `plans/`, `prompts/`, `reports/`, or similar ad hoc locations.\n  - Create new internal artifacts from the shared Notion template at `https://www.notion.so/3461834d8eff81318a4cccaf19b0de51` instead of creating free-form pages from scratch.\n  - The template's visual settings such as `Small text` and `Full width` are maintained manually in Notion. Preserve them by duplicating the template when possible.\n  - If the user asks for a PRP workflow, create or update the artifact in Notion instead of creating `plans/*.md`.\n  - If the user asks for a report or findings export, prefer a Notion page unless they explicitly ask for a repo file.\n  - Set a page icon that matches the artifact type or content when creating the page. Prefer clear, stable icons such as `📗` for PRPs/plans, `📊` for reports, `🔎` for investigations, `✍️` for prompts, and `🛠️` for execution notes.\n  - If an existing markdown file looks ambiguous, ask before deleting or migrating it.\n- Never use `data-testid` as a styling hook. It is reserved for tests. Use semantic attributes such as `data-table-element-role` instead.\n\n## NPM Registry Queries\n\nThis repo uses `devEngines.packageManager` with `\"onFail\": \"error\"` in the root `package.json`. `pnpm info` and `pnpm view` can fail because they delegate to npm and npm 11 rejects the request in this setup.\n\nUse npm from outside the repo instead:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Workspace Layout\n\n### Main packages\n\n- `packages/data-table` - virtualized data table package; currently the most active package in this repo\n- `packages/react-virtuoso` - main virtualization library\n- `packages/message-list` - chat/message list package\n- `packages/masonry` - masonry layout package\n- `packages/gurx` - reactive state library used by older Virtuoso internals\n- `packages/reactive-engine-*` - lower-level engine packages used by `data-table`\n\n### Apps and examples\n\n- `apps/virtuoso.dev` - Astro/Starlight docs site\n- `examples` - separate workspace for shared Ladle/integration examples\n- `packages/react-virtuoso/examples` - example pages used by `packages/react-virtuoso/e2e`\n\n## Plugin Distribution\n\n- This repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n- After merge, public Codex plugin install is:\n\n  ```bash\n  codex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\n  codex plugin add virtuoso-skills@virtuoso\n  ```\n\n- Source skill files live at `packages/virtuoso-skills/skills/<name>/SKILL.md`.\n- Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly. They are generated by `pnpm build:skills`.\n- Do not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Commands\n\n### Root\n\n- `pnpm build` - build all workspaces\n- `pnpm build:skills` - regenerate skill references and public skill mirrors\n- `pnpm validate:skills` - validate Claude and Codex plugin manifests\n- `pnpm typecheck` - run type checking across workspaces\n- `pnpm lint` - run package lint scripts across workspaces\n- `pnpm format` - format with `oxfmt`\n- `pnpm format:check` - check formatting with `oxfmt`\n- `pnpm test` - run workspace test scripts\n- `pnpm e2e` - run workspace e2e scripts\n- `pnpm lint:md` / `pnpm lint:md:fix` - markdown lint / fix\n- `pnpm ci` - full CI sequence: setup, build, typecheck, lint, markdown lint, test, e2e\n- `pnpm dev:docs` - start the docs site\n- `pnpm changeset-add` - add a changeset\n\n### `packages/data-table`\n\n- `pnpm build` - `tsc && vite build`\n- `pnpm typecheck` - `tsgo -b --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - `vitest run --browser.headless`\n- `pnpm check` - `format:check + lint + typecheck`\n- `pnpm dev` - run Ladle serve mode\n- `pnpm dev:build` / `pnpm dev:preview` - Ladle build / preview\n\n### `packages/react-virtuoso`\n\n- `pnpm build` - Vite build\n- `pnpm typecheck` - `tsgo --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - Vitest\n- `pnpm test:watch` - Vitest watch\n- `pnpm e2e` - Playwright\n- `pnpm ladle` - preview examples in Ladle\n\n### `apps/virtuoso.dev`\n\n- `pnpm dev` - Astro dev server\n- `pnpm build` - `shadcn build && astro build`\n- `pnpm lint` - `oxlint --type-aware --type-check && astro check`\n- `pnpm format` / `pnpm format:check` - `oxfmt` plus Prettier for `.astro`\n\n## Documentation Workflow\n\nThe docs site contains generated package docs. Do not edit the generated content under:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nEdit the source docs in the package instead:\n\n- `packages/data-table/README.md` and `packages/data-table/docs/*.md`\n- `packages/react-virtuoso/README.md` and `packages/react-virtuoso/docs/*.md`\n- `packages/message-list/README.md` and `packages/message-list/docs/*.md`\n- `packages/masonry/README.md`\n- `packages/gurx/README.md`\n\nAfter product docs changes:\n\n- run `pnpm lint:md` from the repo root\n- if docs-site content or registry code changed, run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n\n## Validation Guidance\n\nChoose the narrowest validation that matches the change:\n\n- `packages/data-table` code changes: start with `pnpm check && pnpm test` in `packages/data-table`\n- `packages/react-virtuoso` code changes: run `pnpm lint && pnpm typecheck && pnpm test`, add `pnpm e2e` when behavior changes\n- docs-only markdown changes: run `pnpm lint:md`\n- docs site or registry changes: run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n- broad cross-package changes: run root `pnpm typecheck && pnpm lint && pnpm test`\n\n## Architecture Notes\n\n- `packages/react-virtuoso` uses the custom `urx` stream/state system under `packages/react-virtuoso/src/urx`. If you touch its internals, follow existing `*System.ts` and `react-urx/` patterns.\n- `packages/data-table` is a separate package built on the `reactive-engine-*` workspaces. When working there, prefer matching existing engine/cell/stream patterns instead of borrowing directly from `react-virtuoso` internals.\n\n## Git Hooks\n\nThis repo uses `lefthook`. Pre-commit hooks format and lint staged files. If you need to skip them for a WIP commit:\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: ...\"\n```\n","isInternal":false,"tokens":2044,"sizeBytes":7633},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## NPM Registry Queries\n\nThis project uses `devEngines.packageManager` with `\"onFail\": \"error\"` in `package.json`, which causes `pnpm info` and `pnpm view` to fail because they delegate to npm, and npm 11 rejects the request. To check package versions, run npm from outside the project directory:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Build Commands\n\nThis is a pnpm workspaces monorepo. Run commands from the root or within specific workspace packages.\n\n### Root-level commands\n\nRun from repository root for all packages:\n\n- Build all: `pnpm build`\n- Lint all (includes type checking): `pnpm lint`\n- Format all: `pnpm format` (oxfmt)\n- Format check: `pnpm format:check`\n- Test all: `pnpm test`\n- E2E tests all: `pnpm e2e`\n- Markdown lint: `pnpm lint:md` / fix: `pnpm lint:md:fix`\n- Full CI: `pnpm ci` (setup, build, lint, lint:md, test, e2e)\n- Release: `pnpm release` (build + publish with changesets)\n- Add changeset: `pnpm changeset-add`\n- Dev docs site: `pnpm dev:docs`\n\n### react-virtuoso package (packages/react-virtuoso/)\n\n- Build: `pnpm run build` (uses vite)\n- Test: `pnpm run test` (vitest)\n- Test watch: `pnpm run test:watch`\n- Run single test: `pnpm vitest <test-file-path>` or `pnpm vitest -t \"<test-name>\"`\n- E2E tests: `pnpm run e2e` (playwright)\n- Lint (includes type checking): `pnpm run lint`\n- Format: `pnpm run format` (oxfmt)\n- Format check: `pnpm run format:check`\n- Dev/preview examples: `pnpm run ladle` (launches Ladle server for browsing examples/ folder)\n\n### virtuoso.dev docs app (apps/virtuoso.dev/)\n\n- Dev server: `pnpm run dev`\n- Build: `pnpm run build`\n- Format: `pnpm run format` (oxfmt + Prettier for .astro files)\n\nAfter docs changes: run `pnpm lint` from the app directory or root.\n\n**IMPORTANT - Documentation Locations:**\n\n**DO NOT EDIT** the following auto-generated directories:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nThese are auto-synced from source files + TypeDoc API via the `docsSync` integration. Any edits will be overwritten.\n\n**To edit package documentation**, modify the source files in each package:\n\n- **data-table**: `packages/data-table/docs/*.md`\n- **react-virtuoso**: `packages/react-virtuoso/README.md` or `packages/react-virtuoso/docs/*.md`\n- **masonry**: `packages/masonry/README.md` or `packages/masonry/docs/*.md`\n- **gurx**: `packages/gurx/README.md` or `packages/gurx/docs/*.md`\n- **message-list**: `packages/message-list/README.md` or `packages/message-list/docs/*.md`\n\n## Plugin Distribution\n\nThis repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n\nEdit source skill content in `packages/virtuoso-skills/skills/<name>/SKILL.md`. Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly; regenerate them with:\n\n```bash\npnpm build:skills\n```\n\nAfter merge, public Codex plugin install uses:\n\n```bash\ncodex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\ncodex plugin add virtuoso-skills@virtuoso\n```\n\nDo not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Monorepo Structure\n\n```text\npackages/\n  react-virtuoso/    - Main virtualization library\n  gurx/             - urx state management (fork/variant)\n  masonry/          - Masonry layout component\n  message-list/     - Chat/message list component\n  tooling/          - Shared build tooling\n\napps/\n  virtuoso.dev/     - Starlight/Astro documentation site\n\nexamples/            - Ladle stories for testing/development\n```\n\n## Architecture\n\n### State Management: urx System\n\nThe codebase uses **urx**, a custom reactive state management system built on streams/observables. Core concepts:\n\n- **Systems**: Stateful data-processing machines composed of streams\n- **Streams**: Can be stateless (signals) or stateful (depots that persist values)\n- **Depots**: Implicit state maintained in stateful streams, transformers (combineLatest), or operators (withLatestFrom, scan)\n- **Input/Output**: Systems receive input via input streams, process via transformers/operators, emit via output streams\n\nKey urx files: `src/urx/` directory contains:\n\n- `system.ts` - System creation and composition\n- `streams.ts` - Stream primitives\n- `pipe.ts` - Stream operators and transformers\n- `actions.ts` - Publishing/emitting\n- `transformers.ts` - Stream transformation utilities\n\n### Component Architecture\n\nThe virtualization logic is split into modular systems in `src/`:\n\n**Core Systems:**\n\n- `listSystem.ts` - Composes all feature systems into the main list system\n- `sizeSystem.ts` - Tracks and manages item sizes (critical for variable-height items)\n- `listStateSystem.ts` - Manages visible item ranges and scrolling state\n- `domIOSystem.ts` - DOM measurements and interactions\n\n**Feature Systems:**\n\n- `groupedListSystem.ts` - Grouped lists with sticky headers\n- `scrollToIndexSystem.ts` - Programmatic scroll positioning\n- `followOutputSystem.ts` - Auto-scroll for chat/feed UIs\n- `initialTopMostItemIndexSystem.ts` - Initial scroll position\n- `scrollSeekSystem.ts` - Placeholder rendering during fast scrolling\n- `windowScrollerSystem.ts` - Window-scrolling mode\n- And many more in `src/*System.ts` files\n\n**React Integration:**\n\n- `react-urx/` - Bridges urx systems to React components\n- `Virtuoso.tsx` - Main list component\n- `VirtuosoGrid.tsx` - Grid layout component\n- `TableVirtuoso.tsx` - Table virtualization component\n- Component interfaces in `component-interfaces/`\n\n### Size Calculation\n\nVariable-sized items work automatically via `sizeSystem.ts`:\n\n- Uses ResizeObserver for measurements\n- Maintains size ranges and estimates\n- No manual height specification needed\n- `correctItemSize()` utility in `utils/` handles size corrections\n\n### E2E Testing\n\nE2E tests in `packages/react-virtuoso/e2e/`:\n\n- Test files: `*.test.ts` (Playwright tests)\n- Example pages: `examples/*.tsx` (rendered in browser for tests)\n- Use Ladle (`pnpm run ladle`) to preview examples during development\n\n## Code Style\n\n- Never use `data-testid` attributes for CSS styling selectors. `data-testid` is reserved for testing only. Use `data-table-element-role` or other semantic data attributes for styling hooks.\n- TypeScript with strong typing; avoid `any`\n- oxfmt: 140 char width, single quotes, no semicolons\n- Naming: camelCase for variables/functions, PascalCase for components\n- Imports: React first, external libs, then internal modules\n- Functional components with hooks preferred\n- Use urx system patterns for state management\n- Error handling: prefer early returns\n\n## Markdown Style Guide\n\nWhen writing or editing markdown documentation:\n\n### Formatting Rules\n\n- Headings: ATX-style (`# Heading` not `Heading\\n=======`)\n- Code blocks: Always use fenced blocks with language specifiers\n\n```typescript\nconst foo = 'bar'\n```\n\n- Lists: Use `-` for unordered lists, indent nested items by 2 spaces\n- Emphasis: Use `_single underscore_` for emphasis, `**double asterisk**` for strong\n- Links: Prefer inline links `[text](url)` for readability\n\n### Content Guidelines\n\n- Start with clear, descriptive headings (H1 for title, H2 for major sections)\n- Use code blocks for all code examples, terminal commands, and file paths\n- Include language identifiers in fenced code blocks (`typescript`, `bash`, `json`)\n- Break long paragraphs into shorter ones (3-5 sentences max)\n- Use tables for structured data comparison\n- Add blank lines before and after headings, lists, code blocks, and tables\n\n### Special Cases\n\n- Inline HTML allowed for badges, complex layouts, or special formatting\n- Bare URLs allowed in reference sections and changelogs\n- Line length not enforced (practical for existing docs)\n- Multiple H1 headings allowed (document sections)\n\n### Linting\n\n- Run `pnpm lint:md` to check markdown files\n- Run `pnpm lint:md:fix` to auto-fix issues\n- Pre-commit hooks automatically lint staged .md files\n- Configuration: `.markdownlint.json` and `.markdownlintignore`\n- If necessary, use `markdownlint` CLI directly, but prefer pnpm scripts\n\n## Code Change Checklist\n\nAfter making code changes, run these commands to verify quality:\n\n### Required (always run)\n\n- `pnpm lint` - Lint and type check (oxlint --type-aware --type-check)\n- `pnpm format` - Format code with oxfmt\n- `pnpm test` - Run unit tests (vitest)\n\n### Conditionally Required\n\n- `pnpm lint:md` - If editing markdown files\n- `pnpm lint:md:fix` - Auto-fix markdown issues\n- `pnpm run e2e` - For UI/behavior changes (Playwright tests)\n- `pnpm run ladle` - To visually inspect component changes\n\n### Quick Full Validation\n\n- `pnpm ci` - Run complete CI pipeline (setup, build, lint, lint:md, test, e2e)\n- `pnpm format:check` - Check if files are formatted without modifying them\n\n### Fixing Issues\n\nFormat issues are auto-fixed by `pnpm format`. oxlint issues must be fixed manually. Configure your editor to:\n\n- Format on save using oxfmt (140 char width, single quotes, no semicolons)\n- Show oxlint warnings/errors\n\nPre-commit hooks will block commits if lint (which includes type checking) fails.\n\n## Development Workflow\n\n1. Make changes in `packages/react-virtuoso/src/`\n2. Run `pnpm format && pnpm lint && pnpm test`\n3. Check examples with `pnpm run ladle` if UI changes\n4. Run `pnpm e2e` for end-to-end validation if needed\n5. Add changeset with `pnpm changeset-add` for versioned changes\n\n## Git Hooks\n\nThis project uses [lefthook](https://github.com/evilmartians/lefthook) for git hooks.\n\n### Pre-commit Checks\n\nOn every commit, the following checks run automatically on staged files:\n\n- **Code formatting**: Formats `.ts/.tsx/.js/.jsx` files with oxfmt and `.astro` files with prettier; changes are auto-staged via `stage_fixed`\n- **Markdown linting**: Validates .md files with markdownlint\n- **Code linting**: Validates code files with oxlint\n\n### Skipping Hooks\n\nIf you need to skip hooks (e.g., WIP commits):\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: work in progress\"\n# Or use git commit --no-verify (not recommended)\n```\n\n### Hook Management\n\n- Configuration: `lefthook.json`\n- Install hooks: `pnpm exec lefthook install`\n- Uninstall hooks: `pnpm exec lefthook uninstall`\n- Run manually: `pnpm exec lefthook run pre-commit`\n","isInternal":false,"tokens":2756,"sizeBytes":10693},{"name":"openai.yaml","path":".codex/skills/docs-guidance/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.codex/skills/docs-guidance/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: 'Docs Guidance'\n  short_description: 'Write readable project docs'\n  default_prompt: 'Use $docs-guidance to revise this documentation for human readability, conceptual flow, and safe examples.'\n","isInternal":false,"tokens":45,"sizeBytes":221},{"name":"diagnostics.md","path":".codex/skills/docs-guidance/references/diagnostics.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.codex/skills/docs-guidance/references/diagnostics.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Diagnostics: Invariants and Smells\n\nTwo complementary frameworks. Invariants describe what every page must teach. Smells describe what to look for when something feels off.\n\nConsult this file when reviewing existing prose, auditing a page against a standard, or deciding which rule to apply to a vague \"this feels off\" comment.\n\n## Documentation Invariants\n\nWhen revising a page, look for the invariant that matters most and make it explicit. Do not let a page merely say an API exists — it should explain the invariant the API protects.\n\n- **Ownership.** Where state, configuration, data, or styling should live.\n- **Identity.** Which keys, names, IDs, fields, or handles must remain stable.\n- **Shape.** The exact shape of data, config, events, return values, or rendered structure.\n- **Lifecycle.** What is created once, reused, reset, persisted, fetched, cancelled, or cleaned up.\n- **Boundary.** Which package, wrapper, component, hook, adapter, or integration owns each responsibility.\n- **Failure mode.** What breaks when the pattern is used in the wrong context.\n- **Escape hatch.** Which API, guide, or example to use when the simple pattern no longer fits.\n\nA page that names its API but does not teach one of these invariants is incomplete. The invariant is the load-bearing knowledge; the API is just the surface for invoking it.\n\n## Readability Failure Smells\n\nRevise when a page has any of these. Grouped by where the smell shows up.\n\n### Opening and framing\n\n- Starts with a library abstraction before explaining the user problem.\n- Begins a section as if the reader already accepted an unstated premise.\n- Opens a section by recommending the alternative the section is meant to compare against. The section is supposed to teach _this_ approach — defer the alternative to a sidebar at the end.\n- Limitation-focused section opens with the recipe instead of the bug the recipe prevents. Lead with the trap, then the fix.\n\n### Concepts and terminology\n\n- Uses a term that only makes sense after reading another page.\n- Drifts into odd phrases, private shorthand, or terms the reader would not naturally use.\n- The same identifier appears in the same doc with different shapes (e.g., two helpers named `fieldsFromRows` returning different types). Rename one to disambiguate.\n\n### Section purpose\n\n- Describes mechanics but not the reason the mechanic exists.\n- Says what is possible but not when to choose it.\n- Explains internal architecture when the user needs a decision rule.\n- The section's audience overlaps poorly with the parent page's audience — it likely belongs in a different page.\n- Has paragraphs that restate the previous paragraph's claim (\"This means…\", \"This keeps things X…\", \"In other words…\"). Meta-commentary that adds no new information; drop it.\n\n### Structure and navigation\n\n- Headings accurate but not helpful to a scanning reader. A heading should announce what the reader will learn, not just name the API.\n- Jumps from install or setup straight into advanced options without a \"what now\" bridge.\n- Cross-link text is vague (\"see X\"). The link text should answer \"why follow this.\" Prefer `[Generated columns](…) for type-aware columns` over `see [Generated columns](…)`.\n\n### Example quality\n\n- Presents \"correct\" code that would be unsafe in real usage.\n- Demonstrates a feature for dynamic or variable data using uniform example rows — silently hiding the variability question the feature exists to answer.\n\n### Feature pages\n\n- Page teaches the data contract or config object thoroughly but never shows what the end user can do with the feature. The reader closes the page knowing the shape but unable to picture the result or the interaction.\n- An interactive feature (the user clicks, drags, toggles, regroups, reorders) is demonstrated only as a static example. No control to click, no model action paired with UI. If the feature is interactive, the example must show the interaction.\n- Page leads with the data shape rather than what the end user sees. Almost always a sign that the writer let the cleanest-to-explain thing become the lead. The fix is to invert: end-user behavior first, contract last.\n- Interactive variations (Group By Category, Sort by Name) appear only as a bullet in a list of options, never as a worked example with the UI surface that fires them.\n\n### Framing tone\n\n- Uses defensive framing for things the library deliberately leaves to the user. (\"The package does not ship X\" → \"X lives in your code so the rules stay obvious next to the table.\")\n- Apologetic phrasing for intentional design choices (\"currently does not re-fetch\") instead of confident phrasing (\"intentionally one-shot\").\n- **Narrative motivation in openers.** Opens with what users want, feel, or expect instead of what the page documents. _\"Users expect headers to do work\"_, _\"A column header is rarely just a label\"_, _\"Wide tables push key context off-screen the moment a reader scrolls\"_ — these are scene-setting. The reader is here for the mechanism; preface drama is friction. Replace with a direct statement of the structure: _\"A column header has four slot positions for additional UI: …\"_.\n- **Anthropomorphism of inanimate code.** Gives UI parts intentions, conflicts, or effort. _\"fighting the label for space\"_, _\"the slots compete\"_, _\"the header expects\"_, _\"the column wants to be wider\"_. Replace with literal mechanics — _\"the slot is positioned outside the label's flex flow\"_ beats _\"the slot doesn't fight the label for space\"_. If the literal mechanic is unclear, the metaphor was hiding that the writer didn't know.\n- **Vibes enumeration.** Lists capabilities as atmospheric color rather than as commitments to coverage. _\"sort the column, open a filter menu, expose a resize handle, surface a 'Pin to left' affordance\"_ — reads like marketing. Either commit to which the page covers (and link out for the rest), or replace the list with the structural fact behind it (_\"Any UI that mounts into one of these four slots\"_). A list of nice-sounding capabilities the page won't actually teach is filler.\n- **\"Rarely just X\" / \"More than just X\" framings.** Opening with a denial of the obvious in order to motivate the topic. _\"A column header is rarely just a label\"_, _\"This isn't just a placeholder\"_, _\"Sticky columns are more than a CSS trick\"_. The reader didn't claim the thing was just X; the denial creates a fake premise the page then dismantles. Drop the denial and state what the thing is.\n\n## Triage rule\n\nIf a page exhibits multiple smells, fix the opening first. A confused opening cascades: every section after it is read through the wrong lens, so smells later in the page often dissolve once the lead is right.\n","isInternal":false,"tokens":1453,"sizeBytes":6723},{"name":"example-craft.md","path":".codex/skills/docs-guidance/references/example-craft.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.codex/skills/docs-guidance/references/example-craft.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Example Craft\n\nRules for the code examples themselves — how to construct them so they teach honestly without hiding the lifecycle, state ownership, or variability that real usage will hit.\n\nConsult this file when writing or auditing the code blocks inside a doc page (live demos, recipe snippets, illustrative JSX).\n\n## Production-Safe Defaults\n\nExamples should model the safe version, not a shortened one that hides the real constraints.\n\n- Use stable instances for stateful objects (no inline `new X()` in render).\n- Use stable identities (`key`, `id`) when items can move, persist, or be restored.\n- Include required cleanup, cancellation, or abort handling when async work is shown.\n- Keep generated data schemas deterministic.\n- Keep persistence examples clear about what is and is not saved.\n- Keep styling examples attached to the right layer: local content styling, wrapper styling, component overrides, or global CSS.\n- Avoid examples that are only short because they hide the lifecycle or state-ownership rule.\n\n## Demo Data Craftsmanship\n\nThe shape of the data matters as much as the code that processes it.\n\n- **Variable-shape data for dynamic-feature examples.** When demonstrating something that handles schema variability, the example rows must actually vary — some rows with extra fields, some without. Uniform example rows silently hide the question the feature exists to answer.\n- **Realistic field counts.** Three uniform columns make every feature look easy; six fields with mixed types stress the layout the same way real usage will.\n- **Schemas the reader recognises.** Use product/order/user shapes the reader has seen before. Resist the urge to invent fictional domains that need their own explanation.\n\n## Identifier Discipline Within a Doc\n\n- **Resolve identifier conflicts.** When you introduce a function, type, or prop name, scan the rest of the page for collisions. Two helpers named `fieldsFromRows` with different signatures in the same doc is a real confusion source. Rename one to disambiguate (`fieldsToShow`, `runtimeFields`, etc.).\n- **Inline helpers vs. canonical helpers.** When an example uses a slim inline helper because the canonical full helper would be overkill, name it differently so the reader can tell which one they're looking at.\n\n## Framing Tone\n\nThe words around the code carry the design's intent. Defensive or apologetic phrasing makes intentional choices look like missing features.\n\n- **Defensive to positive.** When the library deliberately doesn't ship a helper, reframe the absence as a benefit: _\"Field scanning lives in your app code so the rules stay obvious next to the table\"_ beats _\"The package does not inspect your row shape for you\"_.\n- **No apologies for design choices.** If the one-shot nature of a feature is intentional, say \"intentionally one-shot\" — not \"currently does not re-fetch\" (which sounds like a missing feature).\n- **Acknowledge limitations honestly.** When a constraint really is a limitation, name it as one and point at the workaround: _\"Offset placeholders cannot be distinguished from real rows. Use schema-compatible placeholders or backend-provided column metadata.\"_ Honest framing beats both apology and silence.\n\n## Component-Example Style\n\nPatterns that recur in component-library docs.\n\n- **`className` over wrapping `<div>`.** When a component accepts a `className`, prefer it to wrapping the render result in a styled `<div>`. Wrapping changes the DOM structure and breaks layout assumptions in many table, grid, and list contexts; using `className` keeps the wrapping consistent with non-example usage.\n- **Use the wrapper component the project ships.** If the project ships a shadcn-style wrapper (`DataTableCell`, `DataTableColumn`), use it in examples instead of the unwrapped primitive. The wrapped form is what users will copy; show that path.\n- **Skip the JSX import boilerplate in snippets.** Inline code blocks in the middle of prose are clearer when they show only the JSX under discussion. Save the full `import` list for live blocks that need to run.\n","isInternal":false,"tokens":827,"sizeBytes":4072},{"name":"page-shapes.md","path":".codex/skills/docs-guidance/references/page-shapes.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.codex/skills/docs-guidance/references/page-shapes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Page Shapes and Section Shapes\n\nTemplates for whole pages (by type) and for recurring section shapes within pages.\n\nConsult this file when writing a new page from scratch, restructuring an existing page, or naming/ordering the sections of a page that already has its content roughed in.\n\n## Page Shapes by Type\n\n### Concept pages\n\nFor pages that introduce a single concept or primitive (a model, a hook, a low-level component). The audience is developers learning the primitive itself.\n\nIf the page describes a feature with a user-facing surface — grouping, filtering, drag-and-drop, sticky pinning, column reorder, visibility toggles — use the **Feature pages** template instead. Concept template is for pure-developer-facing primitives.\n\n- Open with the reader problem.\n- Explain the concept in ordinary words.\n- Show the data/state/API contract.\n- Show the smallest safe example.\n- Add runtime/lifecycle caveats.\n- Link to one deeper example or adjacent decision.\n\n### Feature pages\n\nFor pages that describe a user-facing capability — grouped rows, filtering, sorting, column reorder, column visibility, sticky pinning, drag-and-drop, expand/collapse. The developer reading the page is configuring something their end user will see or interact with.\n\nThe temptation in feature docs is to lead with the data shape or config object because it's the cleanest thing the writer understands. Resist it — the data shape is what makes the feature work, not what makes the feature interesting to the reader.\n\n- **What the end user sees.** Open with a sentence (or a small \"this is the result\" demo) describing the visible outcome.\n- **What the end user does.** If the feature is interactive, demonstrate the interaction with a live example that includes the UI control the user clicks, toggles, or drags. Static-only demos of interactive features are a smell — see [diagnostics.md](diagnostics.md#feature-pages).\n- **How to wire the interaction.** The API surface that exposes the behavior (model action, hook, prop) paired with the UI piece that triggers it. Treat the UI and the model wiring as one recipe; splitting them hides the half the reader came for.\n- **The data contract or config shape.** Only after the interaction has been shown. The contract is in service of the recipe above it.\n- **Variations.** Local vs remote, simple vs interactive, single-level vs multi-level — each as a sub-section that re-uses the user-behavior-first ordering.\n- **Limits and escape hatches.** Close with what doesn't fit and where to go next.\n\n### Example pages\n\nFor pages that demonstrate a recipe end-to-end.\n\n- First paragraph: \"This example uses X to solve Y.\"\n- `## APIs used` — short list grouped by job, not exhaustive reference.\n- Runnable example code.\n- Closing section named `## Notes`, `## When this doesn't fit`, `## When to outgrow this shape`, or `## What this leaves out`.\n\n### Troubleshooting and performance pages\n\n- Lead with symptoms and decision rules.\n- Prefer tables mapping symptom → fix.\n- Avoid implying users should tune defaults without a concrete symptom.\n\n### Installation pages\n\n- State what gets installed or generated.\n- Distinguish local editable code from package dependencies.\n- Show the smallest successful setup.\n- Link to the next required setup tasks.\n\n### Migration pages\n\n- State what is not a drop-in replacement.\n- Map old concepts to new concepts.\n- Give a recommended sequence, not a pile of independent facts.\n- End with the core mental-model shift.\n\n## Section Shapes (within pages)\n\nThese shapes recur across multiple page types — use them wherever they fit.\n\n### Limitation-focused section (gotcha-led)\n\nWhen a section exists because of a trap — \"Placeholders\" in a runtime-discovery page, \"Empty responses\" in a remote-data page, \"Schema drift\" in a persistence page:\n\n1. Open with the trap. Describe the bug the section's recipe prevents.\n2. Show the recipe.\n3. Add sidebar advice for cases the recipe still doesn't cover.\n\nExample opening: _\"The field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data.\"_ — then the schema-compatible placeholder recipe.\n\n### Comparison / \"When this does not fit\" section\n\nWhen enumerating alternatives at the end of a page, use **named bullets** with bold term labels:\n\n```markdown\n- **Backend-defined schemas.** Use when the backend already knows the shape.\n- **Statically-known schemas.** Use when the schema is in code but the JSX would be repetitive.\n- **Runtime column-management UI.** Lift the field list into your own state when the user can add or reorder fields after mount.\n```\n\nClose with a one-sentence summary of when the current page's approach _does_ fit.\n\n### Topic-first, alternative-second\n\nNever open a section by recommending the alternative it is meant to compare against. Pattern:\n\n1. Show the section's actual topic.\n2. After the example, add a sidebar paragraph for the alternative — \"When the backend can return column metadata alongside the rows, prefer that.\"\n\n### Parameter / config explanation block\n\nWhen showing what arrives at a render prop, hook, or callback, use bullets per parameter:\n\n```markdown\nThe render function is called with `{ data, model }`:\n\n- `data` is X — it does not change when Y.\n- `model` is Z — available for the rare case where W.\n```\n\nEasier to scan than parallel prose sentences.\n\n### \"How the contract looks\" before the example\n\nWhen behavior depends on a contract (data shape, config shape, event payload), render the shape inline before the runnable example:\n\n````markdown\nThe render function receives:\n\n```ts\n{\n  data: readonly Row[]\n  model: DataModelHandle<Row>\n}\n```\n\nThen the example…\n````\n\nThe shape grounds the example. Without it, the reader has to reverse-engineer the contract from the example body.\n","isInternal":false,"tokens":1236,"sizeBytes":5833},{"name":"SKILL.md","path":".codex/skills/docs-guidance/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.codex/skills/docs-guidance/SKILL.md","title":"docs-guidance","category":"anthropic-skill","format":"markdown","content":"---\nname: docs-guidance\ndescription: Revises project documentation for human readability, conceptual flow, and example safety. Covers concept pages, example pages, troubleshooting, installation, and migration docs. Use when writing or reviewing docs, when the user says a page \"feels off\", \"feels weird\", \"feels sudden\", or \"needs attention\", when asked to make a doc clearer or to rewrite a section, when fixing cold or abrupt openings, when a section opens with the API mechanism instead of the user's problem, or when comparing prose against a readability standard.\n---\n\n# Docs Guidance\n\nUse this skill for project documentation work. Treat docs quality as a product concern: the reader should understand where they are, why the topic exists, what shape the relevant data or API has, and when the shown pattern stops fitting.\n\nThis skill is intentionally general. Apply it to package docs, guides, examples, troubleshooting pages, migration notes, and installation pages across the repo.\n\n## Workflow\n\n1. Start from the reader's task, decision, or confusion, not from the API list.\n2. Identify the core contract the page must teach: state ownership, data shape, lifecycle, render boundary, persistence keying, configuration shape, backend shape, or control surface.\n3. Rewrite around the path: user problem → concept → contract → runnable example → limits and next step.\n4. Keep examples safe for real usage, not just short.\n5. End with a concrete escape hatch: where to go when this example no longer fits.\n\n## Core Readability Rules\n\nThese are the non-negotiables. Most doc-quality problems trace to violating one of them:\n\n- **Start from the reader's situation.** Open each page with the user problem, decision, or concrete setup. Never begin with an unexplained abstraction or API name unless the page is explicit API reference.\n- **Describe, don't dramatize.** State what the thing is and what it does in literal terms. Don't open with what users feel or expect (\"users expect…\", \"users develop opinions about…\"); don't give code or UI intentions, conflicts, or effort (\"the slot fights for space\", \"the header expects to do work\"); don't enumerate capabilities as atmospheric color when the page only covers some of them. Narrative motivation, anthropomorphism, and vibes lists all signal the writer didn't commit to a direct statement of the mechanism. See [diagnostics.md → Framing tone](references/diagnostics.md#framing-tone) for the specific smells.\n- **Define concepts before using them.** Don't introduce project terms (`engine`, `slot`, `pipeline`, `marker`, `adapter`, `state cell`, `registry`, `provider`) without grounding them in the user's mental model first.\n- **Explain why before how.** Show the pressure that creates the API — rows need stable identity, requests can be superseded, persisted state needs a stable key — before showing the call signature.\n- **For feature pages, lead with user-facing behavior.** If a feature has an end-user surface (something the user sees, clicks, drags, toggles), demonstrate that surface — visibly, interactively when it's interactive — before the developer-facing config or data contract. The developer is reading the page to enable an end-user behavior; the end-user behavior is the topic, not the config that supports it.\n- **Show the contract just before the recipe it grounds.** When you're about to drop a code block whose behavior depends on a contract, render the data/state/config shape one paragraph before the code so the example doesn't have to be reverse-engineered. This is a within-section pattern, not a whole-page ordering rule — at page level, the user-facing behavior (for feature pages) or the reader's problem (for concept pages) comes first.\n- **End with a concrete next step.** Every section should exit with a pointer to the next decision, deeper example, or escape hatch.\n\n## When to consult deeper material\n\nLoad these references when the work calls for them. Keeping them out of this file is deliberate — load only what the current task needs.\n\n- **[references/diagnostics.md](references/diagnostics.md)** — the invariants framework (what every page must teach) and the failure smells (what to look for when reviewing). Consult when reviewing, diagnosing, or auditing existing prose.\n- **[references/page-shapes.md](references/page-shapes.md)** — templates for concept, example, troubleshooting, installation, migration, and limitation-focused pages, plus templates for section-level shapes (gotcha-led, comparison, topic-first-alternative-second). Consult when writing a new page or restructuring an existing one.\n- **[references/example-craft.md](references/example-craft.md)** — rules for the code examples themselves: stable instances, variable-shape demo data, identifier conflict resolution, defensive-to-positive framing, and styling-layer choices. Consult when writing or auditing example code blocks.\n\n## Final Check\n\nBefore finishing, confirm:\n\n- Can a reader understand the first paragraph without reading another page?\n- Are all public terms introduced before use?\n- Does every code example model safe state ownership and lifecycle?\n- Does every advanced mechanism explain the user pressure that creates it?\n- Does the page tell the reader what to do next when this pattern is not enough?\n","frontmatter":{"name":"docs-guidance","description":"Revises project documentation for human readability, conceptual flow, and example safety. Covers concept pages, example pages, troubleshooting, installation, and migration docs. Use when writing or reviewing docs, when the user says a page \"feels off\", \"feels weird\", \"feels sudden\", or \"needs attention\", when asked to make a doc clearer or to rewrite a section, when fixing cold or abrupt openings, when a section opens with the API mechanism instead of the user's problem, or when comparing prose against a readability standard."},"isInternal":false,"tokens":1089,"sizeBytes":5322},{"name":"CLAUDE.md","path":"packages/data-table/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/CLAUDE.md","title":"Data-table Skill","category":"anthropic-skill","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Development Commands\n\n- `pnpm build` - Build the library using TypeScript compiler and Vite\n- `pnpm lint` - Run linting with oxlint (type-aware)\n- `pnpm typecheck` - Run TypeScript type checking without emitting files\n- `pnpm test` - Run tests with Vitest (node and browser projects)\n- `pnpm dev` - Start Ladle development server for component stories\n- `pnpm docs:build` - Generate TypeDoc documentation\n- `pnpm docs:serve` - Serve generated documentation\n- `pnpm format` - Format code with oxfmt\n- `pnpm format:check` - Check formatting without modifying files\n\n## After Making Changes\n\nRun these commands after modifying code:\n\n1. `pnpm format` - Format modified files\n2. `pnpm lint` - Check for lint errors and fix any issues\n3. `pnpm typecheck` - Verify no TypeScript errors\n4. `pnpm test` - Run tests when changes affect component logic, state management, or virtualization behavior\n\nGit hooks (via Lefthook) automatically run formatting and linting on pre-commit, and typecheck + tests on pre-push.\n\n## Architecture Overview\n\nThis is a React virtualized data table component with both column and row virtualization. The architecture is based on a reactive state management system using `@virtuoso.dev/reactive-engine`.\n\n### Core Components\n\n- **VirtuosoDataTable** (`src/VirtuosoDataTable.tsx`) - Main React component that renders the virtualized data table\n- **VirtualizedTableContent** (`src/VirtualizedTableContent.tsx`) - Renders the table content with row and column virtualization\n- **Row** (`src/Row.tsx`) - Individual row renderer\n- **Column** (`src/Column.tsx`) - Column renderer with virtualization\n- **Cell** (`src/Cell.tsx`) - Cell renderer\n- **ColumnHeader** (`src/ColumnHeader.tsx`) - Column header renderer\n- **Data State** (`src/data.ts`) - Holds data cells (`data$`, `context$`, `totalCount$`)\n\n### State Management Architecture\n\nThe component uses a realm-based reactive system with cells:\n\n- **Cells** - Hold state values (e.g., `data$`, `context$`, `totalCount$`)\n- **Realm** - Manages the reactive graph and subscriptions\n\nKey state modules:\n\n- `src/data.ts` - Core data cells\n- `src/dom.ts` - DOM measurements and scroll handling\n- `src/row-state.ts` - Row rendering state management and visible row computation\n- `src/column-state.ts` - Column virtualization state\n- `src/sizes.ts` - Size tree management for virtualization\n- `src/state-flags.ts` - Scroll direction and bottom detection\n\n### Virtualization System\n\n- **Size Tree** (`src/sizing/AATree.ts`) - AA tree implementation for efficient size tracking\n- **Offset Calculations** (`src/sizing/` directory) - Handles item positioning and measurements\n- **Resize Observer** - Tracks item size changes dynamically\n- **Viewport Management** - Renders only visible items with buffer zones\n\n### Scroll Behavior Management\n\n- **Scroll-to-Row** (`src/scroll-to-row.ts`) - Handles programmatic scrolling to specific rows\n- **Smooth Scrolling** (`src/useSmoothScroll.ts`) - Custom smooth scroll implementation\n- **Mobile Safari Fixes** (`src/reverse-scroll-fix.ts`) - Platform-specific scroll behavior corrections\n\n### Testing Infrastructure\n\nTests are organized into two Vitest projects:\n\n- **Node Tests** - Pure logic tests in `tests/node/` and `src/sizing/tests/node/`\n- **Browser Tests** - Component tests using Playwright browser in `tests/browser/` and `src/tests/browser/`\n  - Setup file: `src/tests/browser/setup.ts`\n- **Ladle Stories** - Component stories in `src/_stories/` for development and visual testing\n- **Testing Context** - `VirtuosoDataTableTestingContext` for controlled test environments\n\n### Test Writing Guidelines\n\n**Use exact assertions, not ranges.** Tests must verify precise expected values:\n\n- Use `toBe(9)` not `toBeGreaterThan(0)` or `toBeLessThan(10)`\n- Use `toHaveLength(3)` not `toHaveLength(expect.any(Number))`\n- Calculate expected values from test constants (e.g., `Math.ceil((CONTAINER_HEIGHT - HEADER_HEIGHT) / ROW_HEIGHT)`)\n\n**Every assertion must be derivable from the test setup.** If you set `COLUMN_COUNT = 6` and `CONTAINER_WIDTH = 300` with `COLUMN_WIDTH = 150`, then assert exactly 2 visible columns, not \"less than 6\".\n\n**Avoid false-positive-prone patterns:**\n\n- `toBeGreaterThan(0)` - passes even when value is wrong\n- `toBeLessThan(TOTAL)` - passes even when virtualization is broken\n- `not.toBe(previousValue)` without asserting the new expected value\n\n**Correct pattern:**\n\n```typescript\nconst visibleColumns = Math.floor(CONTAINER_WIDTH / COLUMN_WIDTH)\nexpect(cells.length).toBe(visibleColumns)\n```\n\n### API Surface\n\nThe component is controlled through the reactive engine rather than a component ref API:\n\n- `engineRef` / `engineId` identify the table instance\n- `scrollToRow$` and `scrollIntoView$` handle programmatic scrolling\n- `scrollLocation$` exposes current scroll position information\n- `cancelSmoothScroll$` cancels the current smooth scroll animation\n\n### Key Design Patterns\n\n1. **Reactive State Management** - All state changes flow through the reactive engine reactive system\n2. **Declarative Data with Reactive Control** - Data is passed declaratively via prop; table operations use engine streams\n3. **Performance Optimization** - Only renders visible items with efficient size caching\n4. **Cross-platform Compatibility** - Handles browser-specific scroll behavior differences\n5. **Internal State Ownership** - The table manages its own state internally. External code does not lift state out of the table and pass it back in as props. Instead, the table's internal state is accessible and controllable through an imperative \"remote control\" pattern (e.g., ref methods, command channels). This avoids the lifted-state antipattern where the developer extracts table state, transforms it, and feeds it back, which creates extra render cycles and race conditions.\n\n## Research Guidelines\n\n- **Must not use articles, docs, or source code from** TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid.\n- **Must not reference external libraries** like TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid, or similar - their architecture is irrelevant to this codebase\n- Focus exclusively on understanding and extending the existing patterns in this repository\n","isInternal":false,"tokens":1448,"sizeBytes":6326},{"name":"01.shadcn.md","path":"packages/data-table/docs/1.installation/01.shadcn.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/1.installation/01.shadcn.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Install With shadcn/ui\ndescription: Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.\nsidebar:\n  label: Shadcn Install\n---\n\nThe shadcn registry copies a `DataTable` wrapper into `@/components/ui/data-table`. The wrapper re-exports the headless engine from `@virtuoso.dev/data-table` and ships pre-styled `DataTable`, `DataTableColumn`, `DataTableColumnHeader`, and `DataTableCell`. You edit the wrapper; the headless package stays installed as a dependency.\n\n## Prerequisites\n\n- A React application with TypeScript\n- Tailwind CSS configured\n- shadcn/ui initialized in the project\n- Path aliases resolvable from the root config shadcn reads. If a project uses a solution-style root `tsconfig.json` that only contains `references`, mirror the `@/*` path mapping there or shadcn may create a literal `@/components/...` directory instead of writing into `src/components/...`.\n\n## Add the registry component\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table\n```\n\nThe command writes the wrapper to `@/components/ui/data-table` and imports `@virtuoso.dev/data-table/styles.css` (structural layout defaults). No extra CSS setup is needed.\n\nFeature-specific wrapper components use the same GitHub registry address. For example, add the resize handle when you enable column resizing:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nAdd the sort header button when sortable column headers should dispatch model actions:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe hosted registry URL still works if you prefer the existing virtuoso.dev registry endpoint:\n\n```bash\nnpx shadcn@latest add https://virtuoso.dev/r/data-table.json\n```\n\n## Icon library\n\nThe wrapper uses [`lucide-react`](https://lucide.dev/) for the loading spinner, error icon, drag grip, and sort button icons. The `npx shadcn add` command installs `lucide-react` as a dependency on first run.\n\nshadcn's `iconLibrary` field in `components.json` only rewrites icon imports for the canonical shadcn registry — it does not touch components from third-party registries like ours. If your project uses a different icon set (`@radix-ui/react-icons`, `phosphor-react`, etc.) and you want the table icons to match, open the installed files under `@/components/ui/data-table` and swap the lucide imports for your library's equivalents. The icons appear in:\n\n- `index.tsx` — `Loader2` (spinner), `AlertCircle` (error state)\n- `column-reorder/reorder-grip.tsx` — `GripVertical` (drag handle)\n- `column-sort/sort-header-button.tsx` — `ArrowUpDown`, `ArrowUp`, `ArrowDown` (sort state)\n\n## Basic styled table\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Audio' | 'Office' | 'Peripherals'\n  price: number\n  stock: number\n}\n\nexport const products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319, stock: 9 },\n]\n```\n\n## Next steps\n\nThe wrapper picks up your shadcn theme automatically (border radius, color tokens, typography). Edit `@/components/ui/data-table` directly for custom row hover states, sticky backgrounds, or denser spacing.\n\nThe two required setup steps after installation are [data model](/data-table/data-model/) and\n[columns](/data-table/columns/). For an inventory of what `npx shadcn add` actually wrote into\nyour project and how to edit it, see [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/);\nfor per-instance visual changes, see [Styling](/data-table/customization/styling/).\n\nRemote models, sticky columns, and instance control all import from `@virtuoso.dev/data-table` directly while the wrapper continues to provide the styled `DataTable`.\n","frontmatter":{"title":"Install With shadcn/ui","description":"Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.","sidebar":{"label":"Shadcn Install"}},"isInternal":false,"tokens":1344,"sizeBytes":5567},{"name":"02.headless.md","path":"packages/data-table/docs/1.installation/02.headless.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/1.installation/02.headless.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Install The Headless Package\ndescription: Use the headless Virtuoso data table package directly when you need full control over styling and composition.\nsidebar:\n  label: Headless Install\n---\n\nThe headless package gives you `VirtuosoDataTable`, `Column`, `ColumnHeader`, and `Cell` without any preset styling. Pick it when you already have a design system, use non-Tailwind styling, or need fully custom markup around the table.\n\n## Install the package\n\n```bash\npnpm add @virtuoso.dev/data-table\n```\n\n`npm`, `yarn`, and `bun` work too.\n\n## Import structural styles\n\nThe package ships layout defaults (column sizing, box model) in a `@layer virtuoso` block. Import the stylesheet once in your app's CSS or entry point:\n\n```css\n@import '@virtuoso.dev/data-table/styles.css';\n```\n\n```typescript\nimport '@virtuoso.dev/data-table/styles.css'\n```\n\nThe `@layer virtuoso` wrapping puts these rules at the lowest cascade priority, so any plain CSS or Tailwind utility class overrides them.\n\n## Minimal headless example\n\n```tsx live wide file=App.tsx\nimport { Cell, Column, ColumnHeader, VirtuosoDataTable, localModel } from '@virtuoso.dev/data-table'\n\nimport { rows } from './data'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTable\n      model={model}\n      style={{\n        height: 360,\n        border: '1px solid var(--border)',\n        borderRadius: 16,\n        overflow: 'hidden',\n      }}\n    >\n      <Column field=\"name\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Product</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"category\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Category</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"price\">\n        <ColumnHeader>\n          {() => (\n            <div\n              style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}\n            >\n              Price\n            </div>\n          )}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div\n              style={{\n                padding: '0 12px',\n                height: 44,\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'flex-end',\n                fontVariantNumeric: 'tabular-nums',\n              }}\n            >\n              {currency.format(Number(cellValue))}\n            </div>\n          )}\n        </Cell>\n      </Column>\n    </VirtuosoDataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## Next steps\n\nThe headless package exposes the full surface area: local and remote data models, instance control, scroll control, and testing utilities. Continue with [data model](/data-table/data-model/) and [columns](/data-table/columns/).\n","frontmatter":{"title":"Install The Headless Package","description":"Use the headless Virtuoso data table package directly when you need full control over styling and composition.","sidebar":{"label":"Headless Install"}},"isInternal":false,"tokens":963,"sizeBytes":3851},{"name":"01.local-data-model.md","path":"packages/data-table/docs/2.data-model/01.local-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/2.data-model/01.local-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Local Data Model\ndescription: Use localModel for in-memory rows, derived views, persistence, and grouped data.\nsidebar:\n  label: Local Data Model\n---\n\nWhen the rows are already in the browser — a hardcoded array, React state, a client-side collection mutated by user actions — `localModel()` is the model that wraps them. It holds the array in memory and exposes actions that mutate or derive the rows the table displays.\n\n```typescript\nconst model = localModel({ data: products })\n```\n\nPass the model to the table:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  {/* columns */}\n</DataTable>\n```\n\n## Where to keep the model\n\nThe model holds reactive state — engine subscriptions, the pipeline's stage cache, persisted action payloads. Recreating it discards that state, so the model instance must outlive renders. Two patterns cover almost every case:\n\n**Module scope** — when the dataset is static and the table is a singleton on the page. This is the simplest possible form and matches how most examples in these docs are written.\n\n```tsx\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport function ProductsTable() {\n  return <DataTable model={model}>{/* columns */}</DataTable>\n}\n```\n\nThe model is shared by every render and every mount of `ProductsTable`. Filter and sort state survives navigations away from the page — usually desirable; occasionally not. Initial config can't depend on props because there's no component scope yet.\n\n**`useState` with lazy init** — inside the component, for everything else: multiple table instances on the same page, initial data sourced from props, dynamic mount/unmount.\n\n```tsx\nconst [model] = useState(() => localModel<Product>({ data: initialProducts }))\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Reach for this whenever the model can't live in module scope.\n\nAvoid `useMemo(() => localModel(...), [])` for this purpose. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which would silently reset the model's subscriptions and state. `useState` is the only hook that guarantees retention.\n\n## Pipelines\n\nSource rows rarely match what a table should actually display. A search box trims them down, a header click reorders them, a toolbar groups them by category. A **pipeline** is the ordered chain of stages that derives the rendered rows from the source array — each stage takes the previous stage's output, transforms it, and hands it to the next one.\n\nTwo properties keep the pipeline predictable as actions land:\n\n- The order is **declared once**, so every action lands in a predictable place instead of racing against the others.\n- Each stage's output is **cached**, so an unrelated stage doesn't rerun when only its successor changes.\n\nDeclare the stage order on the model, then attach each handler to the stage it belongs to:\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter', 'sort'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      handler: ({ data, payload }) => data.filter((row) => row.category === payload),\n    },\n    sort: {\n      stage: 'sort',\n      handler: ({ data }) => data.toSorted((a, b) => a.name.localeCompare(b.name)),\n    },\n  },\n})\n```\n\nStage names are open labels — `filter`, `sort`, and `group` are the conventional ones, but you can split work across whatever stages your table needs (`enrich`, `paginate`, `dedupe`). Inside each handler, `data` is whatever the previous stage produced: filter sees the source rows, sort sees the filtered rows, a later group stage would see the sorted ones.\n\nWhen the `filter` payload changes, the filter stage and the later sort stage both rerun. When only `sort` changes, the cached filter result is reused — the difference matters once filtering or grouping touches thousands of rows.\n\n## Source mutators\n\nAn action declared without a `stage` mutates the backing rows. Source mutators invalidate the full pipeline because every derived stage depends on the new source.\n\n```typescript\nconst model = localModel({\n  data: products,\n  actions: {\n    renameProduct: {\n      handler: ({ source, payload }) => source.map((row) => (row.id === payload.id ? { ...row, name: payload.name } : row)),\n    },\n  },\n})\n\nmodel.send({ action: 'renameProduct', payload: { id: 'SKU-001', name: 'Desk Pro' } })\n```\n\n`model.send({ action, payload })` is how buttons, inputs, effects, and table controls dispatch actions. The action name picks the handler; the payload is whatever your handler expects.\n\n## React state updates\n\nWhen rows arrive from React state, a websocket, or a parent workflow, push the next row set into the model instead of recreating it — see [Where to keep the model](#where-to-keep-the-model) for why the instance must stay stable.\n\n```tsx\nconst [model] = React.useState(() => localModel<Product>({ data: initialProducts }))\n\nReact.useEffect(() => {\n  model.setData?.(products)\n}, [model, products])\n```\n\nFor grouped rows, `model.setData(nextRows, nextGroups)` updates rows and markers together.\n\nIf the row schema changes intentionally, remount the table with a new `key` so runtime columns can discover the new first result. See [Runtime Columns](/data-table/columns/runtime-columns/#rediscovering-columns).\n\n### Updating rows in place\n\n`setData` resets the table's size tracking along with the rows — every row is measured again, which briefly unmounts and remounts visible cells. When a refresh only changes row values — a live price tick, a status flip — and the row set is otherwise identical, `model.updateData(nextRows)` skips that reset:\n\n```tsx\nReact.useEffect(() => {\n  model.updateData?.(products)\n}, [model, products])\n```\n\n`updateData` requires the caller to guarantee a strict contract between `nextRows` and the current rows:\n\n- the row count is unchanged\n- each index still holds the same logical row — no insertions, deletions, or reorders\n- `computeRowKey` returns the same key for each row before and after the update\n- group markers are unaffected — `updateData` never takes a `groups` argument, since group placement is index/level metadata a value-only update can't legitimately change\n- row values that do not determine the row key may change freely\n\nCached sizes are retained as estimates for the new values and corrected by `ResizeObserver` for any row that's actually mounted. If the row count doesn't match, the table falls back to a full reset and logs a dev warning.\n\nA pipeline stage that sorts or filters on a field the update just changed can reorder rows at the same indices, which breaks the \"same logical row per index\" contract and misattributes cached heights to the wrong row. `updateData` on a model with a `pipeline` logs a dev warning for this reason — use `setData` there instead.\n\nA custom model (built directly on the model protocol rather than `localModel`) opts into the same behavior by returning `operation: 'update'` on the `DataResult` it emits.\n\n## Grouping\n\nWhen rows naturally cluster — sales by region, products by category, employees by department — group headers belong between the rows they introduce. The library keeps those headers inside the same flat `data` array rather than nesting rows under each header, so the pipeline, sizing, and virtualization treat them as ordinary rows. A separate `groups` array tells the table which entries are headers and how deeply they nest.\n\n### Static groups\n\nWhen the grouping is computed up front, pass pre-grouped rows and markers to `localModel`:\n\n```tsx\nconst rows = [\n  { label: 'Office' },\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  { label: 'Peripherals' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals' },\n]\n\nconst groups = [\n  { index: 0, level: 0 },\n  { index: 3, level: 0 },\n]\n\nconst [model] = useState(() => localModel({ data: rows, groups }))\n```\n\n`index` points at the group row in `data`. `level` is the nesting depth — `0` for top-level headers, `1` for headers nested under them, and so on.\n\n### Multiple levels\n\nHigher `level` values render deeper and stick under their parent when the user scrolls. A two-level `region → country → city` table declares each region marker at `level: 0` and each country marker at `level: 1`:\n\n```typescript\nconst data = [\n  { groupName: 'North America' },\n  { groupName: 'USA' },\n  { name: 'New York', population: 8_336_000 },\n  { name: 'Los Angeles', population: 3_898_000 },\n  { groupName: 'Canada' },\n  { name: 'Toronto', population: 2_794_000 },\n  { groupName: 'Europe' },\n  { groupName: 'UK' },\n  { name: 'London', population: 8_982_000 },\n]\n\nconst groups = [\n  { index: 0, level: 0 }, // North America\n  { index: 1, level: 1 }, // USA\n  { index: 4, level: 1 }, // Canada\n  { index: 6, level: 0 }, // Europe\n  { index: 7, level: 1 }, // UK\n]\n```\n\nThe table treats `level` as a depth hint, not a tree. There's no `parent` field — a `level: 1` header is rendered as a child of whichever `level: 0` header most recently preceded it in `data`, and sticky headers reserve a row per active level on screen. Skipping levels or reordering markers is allowed; the table still renders every row in the order you provided.\n\n### Computing groups in a pipeline\n\nWhen the grouping dimension is interactive — \"group by category\" toggling to \"group by status\" — keep the source rows flat and compute markers inside a `group` pipeline stage. The handler returns `{ data, groups }` instead of a bare array, and the pipeline updates rows and markers atomically:\n\n```tsx\nfunction groupBy<T extends Record<string, unknown>>(rows: T[], field: keyof T | null) {\n  if (!field) return { data: rows, groups: [] as { index: number; level: number }[] }\n\n  const buckets = new Map<string, T[]>()\n  for (const row of rows) {\n    const key = String(row[field])\n    let bucket = buckets.get(key)\n    if (!bucket) buckets.set(key, (bucket = []))\n    bucket.push(row)\n  }\n\n  const data: (T | { label: string })[] = []\n  const groups: { index: number; level: number }[] = []\n  for (const [key, bucketRows] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${String(field)}: ${key} (${bucketRows.length})` })\n    data.push(...bucketRows)\n  }\n  return { data, groups }\n}\n\nconst [model] = useState(() =>\n  localModel({\n    data: products,\n    pipeline: ['filter', 'group'],\n    actions: {\n      filter: {\n        stage: 'filter',\n        handler: ({ data, payload }) => (payload ? (data as Product[]).filter((row) => row.category === payload) : data),\n      },\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupBy(data as Product[], payload as keyof Product | null),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'group', payload: 'category' })\n```\n\nStage order matters here. With `['filter', 'group']`, the filter narrows the source rows and the group stage buckets only what survived — the natural pairing. Putting `group` before `filter` would force the filter handler to special-case header rows, which is rarely worth the complexity.\n\nA handler that returns `{ data, groups }` updates both. Later stages that return a bare array reuse the most recent `groups` from earlier in the pipeline, so a `sort` after `group` doesn't need to know about markers as long as it preserves their indexes (typically by sorting within each bucket).\n\nTo push pre-grouped rows from outside the pipeline entirely — for example after a parent fetch — call `model.setData(nextRows, nextGroups)`.\n\n### Connecting the UI\n\n`<GroupHeaderCell>` renders the entries marked as headers. It receives the original row and the marker's `level`, so styling can branch on nesting depth:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  <GroupHeaderCell>\n    {({ row, level }) => (\n      <div\n        style={{\n          paddingLeft: 12 + level * 16,\n          fontWeight: level === 0 ? 700 : 500,\n          background: level === 0 ? '#e8e8e8' : '#f4f4f4',\n        }}\n      >\n        {(row.data as { groupName: string }).groupName}\n      </div>\n    )}\n  </GroupHeaderCell>\n  {/* columns */}\n</DataTable>\n```\n\n`row.data` is whatever object you pushed at that index in `data` — the shape is yours to define, the cell just renders what you return. Group rows are virtualized like every other row, so keep the rendered markup light.\n\nFor the full render-side reference — remote-paginated grouping, sticky-header behavior, and a runnable end-to-end example — see [Grouped Rows](/data-table/grouped-rows/).\n\n## Action persistence\n\nPersist action payloads — search terms, filter values, sort choice, grouping mode — so user intent survives reloads. Row data is never persisted.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: true,\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPass an object instead of `true` to set a custom storage key or define empty-state handling.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: {\n        key: 'categoryFilter',\n        isEmpty: (value) => !value,\n      },\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPair this with `modelStatePersistenceAdapter()` from [State Persistence](/data-table/state-persistence/) to save model action state alongside column width, order, and visibility.\n\n`isEmpty` skips a payload from saved state when it represents \"no active filter\" — empty strings, `\"all\"` selections, `null` values.\n","frontmatter":{"title":"Local Data Model","description":"Use localModel for in-memory rows, derived views, persistence, and grouped data.","sidebar":{"label":"Local Data Model"}},"isInternal":false,"tokens":3273,"sizeBytes":13929},{"name":"02.remote-data-model.md","path":"packages/data-table/docs/2.data-model/02.remote-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/2.data-model/02.remote-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Remote Data Model\ndescription: Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.\nsidebar:\n  label: Remote Data Model\n---\n\nWhen a table's rows live behind an API and there are too many to load up front — paginated catalogs, search results, infinite feeds — `remoteModel()` is the model that handles the back-and-forth with your backend.\n\nYou give it a `fetch` function that loads a slice of rows. The model calls that function at the right moments (the user scrolled into rows that haven't loaded yet, a search changed, an action fired), feeds the results back into the table, and cancels requests that have been overtaken by newer ones.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n    placeholder: loadingProduct,\n  })\n)\n```\n\n## Where to keep the model\n\nThe model carries live state across renders — requests still in flight, the controllers that can cancel them, persisted action payloads, and the bookkeeping that decides what happens when actions overlap. Recreate the model on every render and that state is thrown away: pending fetches get aborted mid-flight and the next render starts the cycle over with fresh ones. The instance has to outlive renders.\n\nFor most remote tables, **`useState` with lazy initialization** is the right place:\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel<Product, Query>({\n    fetch: fetchProducts,\n    initialParams: { category, search },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n  })\n)\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Use this whenever initial params come from URL state, props, or anything else the surrounding component has access to but a module does not.\n\n**Module scope** works for genuinely static config — a fixed endpoint, no per-route params, one table per page:\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { category: 'all', search: '' },\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nBe aware that every mount of every consumer shares the same instance, and therefore the same fetch lifecycle and persisted action state. That can be a feature (cached results survive navigation) or a footgun (two routes appearing to share filter state).\n\nAvoid `useMemo(() => remoteModel(...), [])`. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which for a remote model means cancelling pending fetches and resetting concurrency state silently. `useState` is the only hook with a retention guarantee.\n\n> The remaining examples on this page write `const model = remoteModel({...})` for brevity, focusing each snippet on the option it's introducing. In real code, wrap the call in `useState(() => ...)` or place it at module scope using the rules above.\n\n## Pagination and infinite scrolling\n\n`remoteModel` ships two built-in patterns for deciding what to fetch as the user scrolls. The right choice depends on how your backend serves data and what kind of scrolling experience the table should deliver.\n\n### Pagination with a known total\n\nReach for **offset mode** when the backend accepts `(offset, limit)` slices and can tell you how many rows exist in total. The table renders placeholder rows for everything that hasn't been fetched, so the scrollbar reflects the full dataset, the user can jump anywhere via the scrollbar or keyboard, and each slice is requested as it enters the viewport.\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nThis is what most search-and-filter UIs want: product catalogs, admin tables, anything backed by a SQL `LIMIT/OFFSET` or an Elasticsearch `from/size` query.\n\n### Infinite scrolling\n\nReach for **append mode** when the backend is cursor-based, the total isn't known up front, or new rows arrive over time — social feeds, message timelines, activity logs.\n\n```typescript\nconst model = remoteModel({\n  mode: 'append',\n  fetch: fetchFeed,\n  onViewportChange: defaultAppendViewportHandler,\n  pageSize: 30,\n})\n```\n\nRows accumulate as the user approaches the end of what's already loaded. The scrollbar only covers the loaded portion because the table can't know how many rows are out there; the loading state shows a footer at the bottom while the next page is fetched.\n\n### Custom fetch windows\n\nWhen neither default matches — the backend uses prefetch hints, variable page sizes, or needs requests coalesced into fewer round-trips — write a custom `onViewportChange`. Given the rendered range and the page size, return one or more slices for the model to fetch:\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: ({ endIndex, pageSize, totalCount }) => {\n    const nextOffset = Math.floor((endIndex + 1) / pageSize) * pageSize\n\n    if (nextOffset < totalCount) {\n      return {\n        fetch: [{ offset: nextOffset, limit: Math.min(pageSize, totalCount - nextOffset) }],\n      }\n    }\n  },\n  pageSize: 50,\n})\n```\n\nOffset-mode handlers return `{ fetch: [{ offset, limit }, ...] }`; append-mode handlers return `{ loadMore: true }`.\n\n## Fetch results\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&search=${params.search}`, {\n    signal,\n  })\n\n  return response.json()\n}\n```\n\nOffset mode expects `{ rows, totalCount }`. Add `groups` when the backend returns a flattened grouped result:\n\n```typescript\nreturn {\n  rows: result.rows,\n  groups: result.groups,\n  totalCount: result.totalCount,\n}\n```\n\nFor backends that define the row schema, return schema metadata next to the rows or keep a separate field list in application state — see [Generating Columns at Runtime](/data-table/columns/runtime-columns/).\n\n`pageSize` controls the slice size. Big enough that ordinary scrolling doesn't request on every small move, small enough that refreshes stay responsive.\n\n`placeholder` is the row shape used while first-page rows are still loading. Include every field your column renderers read.\n\n## Remote actions\n\nRemote actions translate UI intent into the next request's params. URL query parameters are the common case; the same pattern works for cursor filters, request bodies, search objects, or anything else `fetch` reads from `params`.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    actions: {\n      search: {\n        strategy: 'supersede',\n        persistence: true,\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      category: {\n        persistence: { key: 'categoryFilter' },\n        handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'search', payload: 'desk' })\n```\n\nReturning new params triggers a refresh. With `persistence: true`, the payload of each action — the search string, the selected category — is saved alongside the rest of the table's persisted state and replayed through the handler the next time the table loads, so users come back to the same filtered view. Pass `capture` and `restore` instead of `true` when you need a custom serialized shape.\n\nFor stateful controls such as sort, filter, group, or search, prefer the model action state as the source of truth. Seed default active controls with `initialActions`, dispatch changes through `model.send()` or `dispatchModelAction$`, and read `modelActionState$` to paint active buttons. Do not mirror sort/filter payloads in React state just to keep controls highlighted; if a control must be controlled by React, explicitly restore that local state when using `modelStatePersistenceAdapter()`.\n\nFor a complete column-header sort control that sends `{ field, direction }` to a remote model, see [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\n## Concurrency and cancellation\n\n`strategy` controls what happens when an action fires while a fetch is still in flight.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    actions: {\n      search: {\n        strategy: 'supersede',\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      exportRows: {\n        strategy: 'queue',\n        handler: ({ params, payload }) => ({ ...params, exportId: String(payload) }),\n      },\n    },\n  })\n)\n```\n\n- `supersede` — cancels the active request for the same action and uses the latest payload. Right for search boxes and filters.\n- `queue` — runs every request in order. Right for ordered workflows where no request can be dropped.\n- `deduplicate` — collapses identical concurrent requests. Right for retry buttons and repeated load commands.\n\nEvery `fetch` call receives an `AbortSignal`. Pass it to `fetch()` or your client library so superseded refreshes, viewport requests, and disconnects cancel cleanly.\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&q=${params.search}`, {\n    signal,\n  })\n  return response.json()\n}\n```\n\n`onError` fires for failed fetches. Aborted requests publish a loading cancellation event instead.\n\n## Loading state\n\nWhile a fetch is in flight the model emits loading events that the table turns into visible UI — a placeholder for the first load, an overlay for refreshes, and a footer for append-mode pagination. The shadcn wrapper renders sensible defaults for all three slots, so most tables don't need to handle loading state by hand. Reach for the events directly only when something outside the table — a global progress bar, a status badge, a logging hook — needs to react to the same lifecycle.\n\nThree phases drive the visible slots:\n\n- `initial` — first unresolved dataset; rendered as `LoadingPlaceholder`.\n- `refresh` — request params changed while old rows stay visible; rendered as `LoadingOverlay`.\n- `end` — append mode is fetching more rows below the current data; rendered as `LoadingFooter`.\n\nOffset viewport fetches show up as placeholder rows rather than table-level loading and don't update `loadingState$`. Manual `loadRange` calls publish `viewport` events for subscribers, but the loading slots ignore them.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nFor external controls, logging, or diagnostics, subscribe to the model's event channel:\n\n```typescript\nconst unsubscribe = model.subscribe((message) => {\n  if (message.type === 'event' && message.payload?.kind === 'loading') {\n    console.log(message.payload.reason, message.payload.phase)\n  }\n})\n```\n\n`loadingState$` (read via `useVirtuosoLoadingState()` or directly inside the table) is updated from the same `initial`, `refresh`, and `end` events.\n","frontmatter":{"title":"Remote Data Model","description":"Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.","sidebar":{"label":"Remote Data Model"}},"isInternal":false,"tokens":2473,"sizeBytes":11459},{"name":"03.row-keys.md","path":"packages/data-table/docs/2.data-model/03.row-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/2.data-model/03.row-keys.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Row Keys\ndescription: Give the table stable row keys so React reconciles rows correctly when the model reorders them.\nsidebar:\n  label: Row Keys\n---\n\n`computeRowKey` returns the React key for a row — the value React uses to decide whether the row in this render is the same logical row as one from the previous render. Provide a stable key (typically derived from `data.id`), and React reconciles the row in place even when its index changes under a sort, filter, group, prepend, or append.\n\n```tsx\n<DataTable computeRowKey={({ data }) => data.id} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## Why row keys matter\n\nWithout `computeRowKey`, the key falls back to the virtual row position. That works for tables whose rows never move, but as soon as the model reorders — a pipeline `filter` stage runs, a `sort` stage reshuffles the result, a new page is appended, a remote refetch returns rows in a new order — every row whose index changed remounts.\n\nThe symptom is usually a user-visible glitch: a search input loses focus mid-keystroke because the row it lives in moved one position, an expanded detail row collapses because its parent shifted, a chart inside a cell redraws because the cell got a new React identity. The data is still correct; the React identity broke. `computeRowKey` is what stops that from happening.\n\nThis is a React reconciliation rule, not a table-specific concern. It applies to any data table where rows can move, regardless of which other table features you use.\n\n## Grouped data\n\nGrouped data mixes data rows and group rows in the same flat array (see [Grouping](/data-table/data-model/local-data-model/#grouping)). The function receives both shapes, so return a key for each:\n\n```tsx\n<DataTable computeRowKey={({ data, index }) => ('id' in data ? data.id : `group:${data.label}:${index}`)} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nGroup rows rarely carry a stable ID from your data, so a composite key built from a label plus index is fine — the table doesn't try to reconcile group rows across regrouping anyway. The important thing is that data rows keep their identity through reorder; group rows are allowed to remount when the grouping dimension changes.\n\n## Function signature\n\n`computeRowKey` receives an object with three fields:\n\n- `data` — the row at this index (the data row or the group row)\n- `index` — the row's virtual position in the table\n- `context` — the table's [ambient context](/data-table/customization/ambient-context/), useful for scoping keys across tenants, saved views, or other instances that might share row IDs\n\n```tsx\n<DataTable computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`} context={{ tenantId: 'acme' }} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nCross-scope key prefixing matters when one component instance is reused for different tenants or views — without it, React would see the same key for what's actually a different row and skip reconciliation it shouldn't have skipped.\n","frontmatter":{"title":"Row Keys","description":"Give the table stable row keys so React reconciles rows correctly when the model reorders them.","sidebar":{"label":"Row Keys"}},"isInternal":false,"tokens":676,"sizeBytes":3024},{"name":"index.md","path":"packages/data-table/docs/2.data-model/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/2.data-model/index.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Choosing a Data Model\ndescription: Give the table a local or remote model before adding table features.\nsidebar:\n  label: Choosing a Model\n---\n\nThe model owns the rows; columns own the rendering. Pick the model that matches where rows come from:\n\n- [`localModel()`](/data-table/data-model/local-data-model/) — rows already in the browser, with filter, sort, group, and edit actions deriving the displayed rows.\n- [`remoteModel()`](/data-table/data-model/remote-data-model/) — rows fetched from an API as request params, cursors, or the rendered range change.\n\nBoth models can persist action state (filter values, sort choice, grouping mode) through opt-in adapters. Row data itself is never persisted.\n\n## Basic local model\n\nUse a local model for in-memory rows. Keep the model stable and pass it to `DataTable` with\n`model={model}`.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Where to go next\n\n- [Local Data Model](/data-table/data-model/local-data-model/) — pipelines, source updates, persistence, grouping (single- and multi-level).\n- [Remote Data Model](/data-table/data-model/remote-data-model/) — pagination vs. infinite scrolling, actions, cancellation, grouped rows, loading UI.\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","frontmatter":{"title":"Choosing a Data Model","description":"Give the table a local or remote model before adding table features.","sidebar":{"label":"Choosing a Model"}},"isInternal":false,"tokens":598,"sizeBytes":2633},{"name":"01.defining-columns.md","path":"packages/data-table/docs/3.columns/01.defining-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/01.defining-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Defining Columns\ndescription: Declare the fields, order, defaults, and basic behavior of a data-table column.\nsidebar:\n  label: Defining Columns\n---\n\n`DataTableColumn` declares a column. For data-backed columns, pass `field`; it reads `row.data[field]`, uses that value as `cellValue`, and uses the field name as the stable column identity for persistence, reordering, resizing, and visibility.\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>Name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor a row `{ name: 'Standing Desk' }`, the cell renderer receives `'Standing Desk'` as `cellValue`. Omitting `DataTableCell` falls back to the field value; omitting `DataTableColumnHeader` on a field-backed column falls back to the field name. In app UIs, prefer explicit headers so you control capitalization, wording, and accessibility.\n\nDisplay-only columns do not need matching row fields. Give them an explicit `id` instead. The id becomes the stable column identity, and `cellValue` is `undefined` unless you provide an `accessor`.\n\n```tsx\n<DataTableColumn id=\"actions\" sticky=\"right\">\n  <DataTableColumnHeader>Actions</DataTableColumnHeader>\n  <DataTableCell>{({ row }) => <ActionsMenu row={row.data} />}</DataTableCell>\n</DataTableColumn>\n```\n\n`id` is not a visible label. For action, selection, expansion, status-icon, and other display-only columns, always provide a `DataTableColumnHeader`. Use visible text such as `Actions` by default; use `sr-only` text only when the product intentionally wants a headerless icon column.\n\nComputed columns also use an explicit `id`, plus an `accessor` when you want the table to provide a derived `cellValue`.\n\n```tsx\n<DataTableColumn id=\"fullName\" accessor={(row) => `${row.firstName} ${row.lastName}`}>\n  <DataTableColumnHeader>Full name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Minimal columns\n\nAdd an explicit header or cell when you need formatting; leave both off when the field name and stringified value are enough.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 260 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\" />\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Declaration order\n\nColumns render in declaration order. Reordering and persistence change the runtime order; the declaration order stays as the baseline used after a reset.\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"name\" />\n  <DataTableColumn field=\"category\" />\n  <DataTableColumn field=\"price\" />\n</DataTable>\n```\n\n## Visibility and sticky\n\n`visible={false}` hides a column by default. Runtime visibility lives in the [Column Visibility](/data-table/columns/column-visibility/) page.\n\n```tsx\n<DataTableColumn field=\"internalSku\" visible={false} />\n```\n\n`sticky=\"left\"` or `sticky=\"right\"` pins a column for row identity, IDs, or actions that should stay visible during horizontal scrolling — see [Sticky Columns](/data-table/columns/sticky-columns/).\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\" />\n<DataTableColumn id=\"actions\" sticky=\"right\" />\n```\n\n## Generated columns\n\nFor dynamic field lists, map them to `DataTableColumn` declarations. Keep the field order deterministic — the field values become the column identities.\n\n```tsx\nconst fields = ['name', 'category', 'stock']\n\n;<DataTable model={model}>\n  {fields.map((field) => (\n    <DataTableColumn field={field} key={field}>\n      <DataTableColumnHeader>{field}</DataTableColumnHeader>\n      <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n    </DataTableColumn>\n  ))}\n</DataTable>\n```\n\nWhen the field list itself is discovered from the first data result, see [Runtime Columns](/data-table/columns/runtime-columns/).\n","frontmatter":{"title":"Defining Columns","description":"Declare the fields, order, defaults, and basic behavior of a data-table column.","sidebar":{"label":"Defining Columns"}},"isInternal":false,"tokens":1113,"sizeBytes":4842},{"name":"02.cell-and-header-renderers.md","path":"packages/data-table/docs/3.columns/02.cell-and-header-renderers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/02.cell-and-header-renderers.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Formatting Cells and Headers\ndescription: Use render params to format cells and headers from column and row state.\nsidebar:\n  label: Formatting Cells\n---\n\nMost columns need formatting that goes beyond stringifying a field — currency values, status badges, stock-level colors, multi-line product names. `DataTableCell` and `DataTableColumnHeader` are where that formatting lives. Each one accepts a render function that the table calls when it paints a body cell or header; only cells inside (or close to) the viewport actually run, so the JSX stays a static, declarative description of the structure while the work scales to large tables.\n\n## Style the correct header surface\n\n`DataTableCell` accepts a `className` prop alongside its render function. The class lands on the table's own cell wrapper, so use it for alignment, font weight, text color, `tabular-nums`, and sticky-specific colors instead of wrapping the render output in another `<div>`.\n\n```tsx\n<DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n```\n\nHeaders have two surfaces because a grow column has both a base width and a rendered width. The table observes the intrinsic header to determine the base width, then assigns spare space to growing columns. If the observed element also stretches across that spare space, its rendered width becomes the next base measurement and the column cannot shrink when the table narrows. Keeping measurement and presentation on separate elements prevents that feedback loop.\n\n`className` styles the intrinsic header content that the table measures. Use it for width, minimum width, padding, typography, and alignment within the base width. `containerProps` styles the outer container after the table assigns the rendered column width. Use it for paint and interaction that must cover the full column, such as a background, hover state, outline, box shadow, cursor, ARIA attribute, or event handler.\n\n```tsx\n<DataTableColumnHeader\n  className=\"min-w-64\"\n  containerProps={{\n    className: 'cursor-pointer hover:bg-muted/50',\n  }}\n>\n  Product\n</DataTableColumnHeader>\n```\n\nThe outer container's geometry belongs to the table. Do not put width, flex, margin, padding, border, display, or `justify-*` utilities on `containerProps`; these can clip the measured content or move edge-aligned header slots. Keep those layout styles on `className`, use `box-shadow` or `outline` for full-width separators, and use `HeaderEnd` for a control that must stay at the rendered column edge.\n\nEvents attached through `containerProps` receive bubbled events from header slots and nested controls. When a header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nKeep width utilities such as `w-*` and `min-w-*` on `DataTableColumnHeader.className`. A `w-full` or flex-growth class on this intrinsic surface resolves only within the measurement boundary; it does not cover the rendered track. Use `containerProps` for that visual result. Do not put width utilities on `DataTableCell`. Use `DataTableColumn grow={...}` when a column should absorb leftover horizontal space. A cell width can make the body content fight the measured grid track and overlap neighboring columns. For cell content that needs truncation or nested layout, keep the cell unconstrained and put `min-w-0` on an inner wrapper:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-64\">Name</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ row }) => <div className=\"min-w-0 truncate\">{row.data.name}</div>}</DataTableCell>\n</DataTableColumn>\n```\n\nEach visible cell is its own element; the table also column- and row-virtualizes, so an extra wrapper inside the render function multiplies by every rendered cell on every scroll. Save nested elements for styling that's only part of the cell content — a status badge, an inline icon, a secondary line of metadata.\n\nThe shadcn wrapper accepts plain text for static headers:\n\n```tsx\n<DataTableColumnHeader>Product</DataTableColumnHeader>\n```\n\nAnd a render function for headers that depend on table state:\n\n```tsx\n<DataTableColumnHeader>{({ columnState }) => (columnState.sticky ? 'Pinned product' : 'Product')}</DataTableColumnHeader>\n```\n\n## Cell values and rows\n\n`cellValue` is the selected field value or the result of the column `accessor`; `row.data` is the whole row. Reach for `row.data` whenever the cell depends on more than one field or renders UI that is not data-backed.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : `${row.data.stock} in stock`}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Render params\n\nThe render function receives a single object whose fields surface what you'd otherwise have to look up by hand. `cellValue` is the shortcut for `row.data[column.field]` on field columns, the result of `accessor(row.data)` on computed columns, and `undefined` on id-only display columns. `row.data` is there for cells that depend on more than one field. `column` and `columnKey` identify which column this invocation is rendering — useful when one extracted renderer drives multiple columns. `column.id` is the stable public identity when you provided one; `column.field` exists only for columns that read a row-data field. `columnState` lets the renderer branch on runtime column state (currently just `sticky`, so headers and cells can style differently when the column is pinned). `overlaidByScrollbar` flips to `true` when a native OS scrollbar overlaps the cell's edge — that's the table telling you trailing UI in this cell is unreachable, so move buttons or actions inboard while the flag is set. Header params have the same shape minus `cellValue` and `row` — a header has no row to render against.\n\n```typescript\ntype CellRenderParams = {\n  cellValue: unknown // row.data[column.field], accessor(row.data), or undefined\n  row: RowItem // the virtualized row\n  column: ColumnDefinition\n  columnKey: string // internal key for this column instance\n  columnState: ColumnState // currently: { sticky }\n  overlaidByScrollbar: boolean // true when a native scrollbar overlaps the cell edge\n}\n\ntype ColumnHeaderRenderParams = Omit<CellRenderParams, 'cellValue' | 'row'>\n```\n\n```tsx\n<DataTableColumnHeader>\n  {({ column, columnState }) => <span className={columnState.sticky ? 'font-semibold' : undefined}>{column.field}</span>}\n</DataTableColumnHeader>\n```\n\n## Extracting renderers\n\nInline render functions become unwieldy past a couple of lines, and they limit what you can do with the renderer itself. Pulling them out into named functions — or full components — buys three things at once: they appear by name in React DevTools (instead of as anonymous arrow functions), one renderer can drive several columns that share the same formatting, and you can wrap the renderer in `React.memo` so it skips re-renders when the row data hasn't changed — useful for expensive cells like rich text or chart sparklines.\n\nThe render-param types are exported and double as the props type for the component:\n\n```tsx\nimport type { CellRenderParams, ColumnHeaderRenderParams } from '@virtuoso.dev/data-table'\n\nfunction StockCell({ row }: CellRenderParams) {\n  return <span>{row.data.stock < 20 ? 'Low stock' : 'Healthy'}</span>\n}\n\nfunction FieldHeader({ column }: ColumnHeaderRenderParams) {\n  return <span>{column.field ?? column.id}</span>\n}\n```\n\nExtracted renderers are ordinary React components. They can call hooks, subscribe to React context, and compose with `React.memo` like any other component. One thing they _don't_ receive is the table's `context` prop — that channel exists for table infrastructure (empty-state, sticky wrappers, scroll element) and doesn't reach cell or header renderers. Close over screen-local state directly, or read it through React context if multiple columns need it.\n\nPass renderers as children of the cell or header:\n\n```tsx\n<DataTableColumn field=\"stock\">\n  <DataTableColumnHeader>{FieldHeader}</DataTableColumnHeader>\n  <DataTableCell>{StockCell}</DataTableCell>\n</DataTableColumn>\n```\n\n## Header slots\n\nA header can host additional UI alongside the label — sort buttons, filter menus, resize handles, drop indicators for reordering. Inline UI (buttons, menus) can go in the header's render function. Two positions don't fit there: a resize handle has to sit on the column boundary independent of label alignment, and a drop indicator has to span the entire header during a reorder drag. The table provides four named slots for these cases:\n\n- `HeaderStart` — before the label, in the header's flex layout. Drag grips, leading icons.\n- `HeaderEnd` — after the label, in the same flex layout. Sort buttons, menu triggers.\n- `HeaderEdge` — pinned to the column boundary, outside label layout. Resize handles.\n- `HeaderOverlay` — spans the whole header. Drop indicators, focus rings.\n\nDeclare slots inside the header; the table positions each one according to its slot type. Slot children receive the same render params as the header, so a sort button can read `column.field` to know which column it's acting on.\n\n```tsx\n<DataTableColumnHeader>\n  {() => 'Price'}\n  <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n</DataTableColumnHeader>\n```\n\nWhen a header has any slots, the label must be a render function — plain string children are only valid for headers without slots. That's why the example above writes `{() => 'Price'}` instead of just `Price`.\n\nThe shadcn wrapper's resize and reorder UI is built on these slots. See [Header Slots](/data-table/customization/header-slots/) for the placement diagram, the `component` prop alternative to render-function children, and complete recipes for sort, filter, and menu slots.\n","frontmatter":{"title":"Formatting Cells and Headers","description":"Use render params to format cells and headers from column and row state.","sidebar":{"label":"Formatting Cells"}},"isInternal":false,"tokens":2511,"sizeBytes":11428},{"name":"03.column-groups.md","path":"packages/data-table/docs/3.columns/03.column-groups.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/03.column-groups.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Groups\ndescription: Organize related columns under shared headers.\nsidebar:\n  label: Column Groups\n---\n\n`ColumnGroup` wraps adjacent columns and renders one shared header above them — \"Inventory\" sitting above stock and status, \"Fulfillment\" above region and category. In a wide table, the grouped header gives the reader a second-tier band to scan instead of counting columns from the left edge to locate a field. Use it when neighbouring columns belong together as a named bundle (inventory metrics, fulfillment details, customer attributes) and the table is wide enough that the bundle isn't obvious from column order alone.\n\n```tsx live wide\nimport { localModel } from '@virtuoso.dev/data-table'\nimport {\n  ColumnGroup,\n  ColumnGroupHeader,\n  DataTable,\n  DataTableCell,\n  DataTableColumn,\n  DataTableColumnHeader,\n} from '@/components/ui/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', category: 'Office', stock: 14, status: 'Ready', region: 'US' },\n  { name: 'USB-C Dock', category: 'Device', stock: 42, status: 'Ready', region: 'EU' },\n  { name: 'Keyboard', category: 'Device', stock: 8, status: 'Low', region: 'APAC' },\n]\n\nconst model = localModel({ data: rows })\nconst groupHeaderClassName = 'flex h-8 items-center justify-center bg-muted/40 px-2 text-xs font-medium text-muted-foreground'\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 300 }}>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Catalog'}</ColumnGroupHeader>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Inventory'}</ColumnGroupHeader>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Fulfillment'}</ColumnGroupHeader>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n    </DataTable>\n  )\n}\n```\n\nGrouping changes only the header. Each column inside a group keeps its own field, cell renderer, width, visibility, and saved state — the body grid never notices the grouping exists. That separation is what makes the next sections work: every per-column feature still applies, with a few group-level twists worth knowing.\n\n## Sticky groups\n\nPin a whole group with `<ColumnGroup sticky=\"left\">` (or `\"right\"`). Every column inside follows the group to the edge during horizontal scroll — it's a shorthand for declaring `sticky=\"left\"` on each child, and the group header stays anchored above its columns.\n\n```tsx\n<ColumnGroup sticky=\"left\">\n  <ColumnGroupHeader>{() => 'Identity'}</ColumnGroupHeader>\n  <DataTableColumn field=\"id\">…</DataTableColumn>\n  <DataTableColumn field=\"name\">…</DataTableColumn>\n</ColumnGroup>\n```\n\nOnly top-level groups can be sticky — `sticky` on a nested `<ColumnGroup>` is ignored. Children inherit sticky-ness from the nearest sticky ancestor, so nested groups don't need their own opt-in.\n\n## Resizing inside a group\n\nResize handles work unchanged on columns inside groups. Each column owns its width override; the group header automatically re-sums from its children on every resize, so dragging \"Stock\" wider stretches the \"Inventory\" band to match. You never set a width on the group itself — there's no group-level resize API, because distributing pixels across child columns is rarely what users mean when they grab a header.\n\n## Reordering with groups\n\nTwo reorder gestures coexist:\n\n- **Move a whole group.** `DraggableGroupHeader` (from the shadcn `column-reorder` registry) makes the group header itself draggable. The user drags \"Inventory\" between \"Catalog\" and \"Fulfillment\" and all of Inventory's columns travel together, keeping their internal order.\n- **Move one column.** `ReorderGrip` on individual column headers keeps working — useful for nudging a column within its group.\n\nBoth gestures respect sticky regions: a left-sticky source can only land among other left-sticky targets, and so on. That guard is built into the registry components, so you can drop them in without filtering by hand.\n\nThere's a footgun in mixing the two: **a single-column drag that crosses a group boundary breaks the visual layout.** The header tree pairs each column with its declared `<ColumnGroup>` regardless of where it now sits in column order, so a column dragged out of group A and dropped between group B's columns produces a body row in the new order but a header band that's no longer contiguous. The whole-group drag never has this problem — it moves every key in one transaction.\n\nTwo practical responses:\n\n- Mount `ReorderGrip` only on ungrouped columns (or only inside groups, never both), so users can't initiate a cross-group single-column drag.\n- Or keep both gestures and accept that cross-group moves split the band. Works for tables where the grouping is informational rather than structural.\n\n`reorderColumnGroup$` is the underlying engine action if you wire your own group-drag UI; `DraggableGroupHeader` publishes to it. See [Column Reordering](/data-table/columns/column-reordering/) for the full reorder surface.\n\n## Nesting\n\n`<ColumnGroup>` can wrap other `<ColumnGroup>` declarations for two-tier categorization — \"Customer\" above \"Personal\" and \"Account\", each above their own columns:\n\n```tsx\n<ColumnGroup>\n  <ColumnGroupHeader>{() => 'Customer'}</ColumnGroupHeader>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Personal'}</ColumnGroupHeader>\n    <DataTableColumn field=\"name\">…</DataTableColumn>\n    <DataTableColumn field=\"email\">…</DataTableColumn>\n  </ColumnGroup>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Account'}</ColumnGroupHeader>\n    <DataTableColumn field=\"plan\">…</DataTableColumn>\n  </ColumnGroup>\n</ColumnGroup>\n```\n\nEach nesting level stacks one extra header row. Two levels usually covers what's worth grouping; deeper hierarchies steal vertical space the data needs.\n\n## Layout notes\n\n- Each nesting level adds a full header row. Keep group labels short and the row height modest — `text-xs` with small padding is the usual register.\n- Group headers render full-width over their span. The label sits inside that bar, so very long labels either wrap or get clipped depending on your CSS.\n","frontmatter":{"title":"Column Groups","description":"Organize related columns under shared headers.","sidebar":{"label":"Column Groups"}},"isInternal":false,"tokens":1609,"sizeBytes":7337},{"name":"04.sticky-columns.md","path":"packages/data-table/docs/3.columns/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/04.sticky-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Sticky Columns\ndescription: Pin high-value columns to the left or right side of a wide table.\nsidebar:\n  label: Sticky Columns\n---\n\nPass `sticky=\"left\"` or `sticky=\"right\"` to a `DataTableColumn` and it stays in place while the rest of the row scrolls under it horizontally. Use this to keep row-identifying columns (typically a product name) visible on the left, and status or action columns visible on the right, when the table is wide enough to scroll sideways.\n\nTwo layout constraints have to hold for the effect to work:\n\n- Pinned columns need an explicit minimum width on the header (`min-w-[…]` on `DataTableColumnHeader`). Without one, the column can collapse to its content width and overlap the scrollable cells sliding past it. See [Column Resizing](/data-table/columns/column-resizing/#where-column-widths-live) for why width belongs on the header, not the cell.\n- Every pinned column subtracts from the scrollable area. Pin one or two columns per edge — pinning more shrinks the scroll region until the feature stops earning its keep.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  owner: ['Avery', 'Morgan', 'Riley'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n  supplier: ['Northwind', 'Contoso', 'Fabrikam'][index % 3]!,\n  warehouse: ['Denver', 'Rotterdam', 'Singapore'][index % 3]!,\n  price: 49 + (index % 8) * 24,\n  stock: (index * 7) % 64,\n  leadTime: `${2 + (index % 5)} days`,\n  margin: `${18 + (index % 6) * 3}%`,\n  updated: `2026-05-${String(1 + (index % 17)).padStart(2, '0')}`,\n}))\n\nconst model = localModel({ data: rows })\nconst scrollColumns = [\n  ['category', 'Category'],\n  ['owner', 'Owner'],\n  ['region', 'Region'],\n  ['supplier', 'Supplier'],\n  ['warehouse', 'Warehouse'],\n  ['price', 'Price'],\n  ['stock', 'Stock'],\n  ['leadTime', 'Lead time'],\n  ['margin', 'Margin'],\n  ['updated', 'Updated'],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\" sticky=\"left\">\n        <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      {scrollColumns.map(([field, label]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader className=\"min-w-[140px]\">{label}</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n      <DataTableColumn field=\"status\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe relevant lines, isolated:\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\">\n  <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"status\" sticky=\"right\">\n  <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Changing pinning at runtime\n\n`sticky` on `DataTableColumn` sets the initial pinning. When the user needs to pin or unpin a column from a toolbar — say, a \"Pin to left\" menu item — publish to the table's `setColumnSticky$` channel instead of re-rendering with a new prop. The [Sticky Columns example](/data-table/examples/sticky-columns/) wires up that external toggle end-to-end; [Controlling the Table](/data-table/controlling-the-table/) covers the engine API the channel belongs to.\n","frontmatter":{"title":"Sticky Columns","description":"Pin high-value columns to the left or right side of a wide table.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1083,"sizeBytes":4250},{"name":"05.column-visibility.md","path":"packages/data-table/docs/3.columns/05.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/05.column-visibility.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Visibility\ndescription: Hide and restore columns with declarative defaults, reactive controls, and persistence.\nsidebar:\n  label: Column Visibility\n---\n\nColumn visibility has two flavours: a declarative default that hides a column at mount, and a runtime channel that flips visibility from a \"Columns\" menu, a saved view, or a similar toggle. The two compose — declare hidden defaults for columns like internal IDs, audit timestamps, and other secondary metadata, then expose a runtime control for users who need to opt back in.\n\n```tsx live wide\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { columns$, localModel, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n]\n\nfunction labelFor(field: string) {\n  return field.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"flex flex-wrap items-center gap-3 rounded-lg border bg-muted/30 px-3 py-2 text-sm\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n\n          return (\n            <label key={key} className=\"flex items-center gap-2\">\n              <input checked={visible} onChange={() => setColumnVisibility({ key, visible: !visible })} type=\"checkbox\" />\n              {labelFor(column.field)}\n            </label>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} size=\"sm\" variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 280 }}>\n        <DataTableColumn field=\"product\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Hiding at declaration time\n\nPass `visible={false}` to a `DataTableColumn` and it stays out of the rendered grid:\n\n```tsx\n<DataTableColumn field=\"internalId\" visible={false}>\n  <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nThe column is still _registered_ — that's what makes it selectable in a picker. It just doesn't take a slot in the header, body, or scroll measurement. There's no layout cost beyond keeping its JSX in the tree, so this is also the right shape for \"hidden by default, sometimes useful\" columns that don't need a runtime toggle.\n\n## Toggling at runtime\n\nPublish to `setColumnVisibility$` (from `@virtuoso.dev/data-table/column-visibility`) with a runtime column key and the boolean you want the column to end up at:\n\n```tsx\nsetColumnVisibility({ key, visible: !isVisible })\n```\n\nRead the current state from `columnVisibilityState$` — a `Map<key, boolean>` with one entry per registered column, declarative defaults already folded in. Consumers don't have to fall back to `columns$` for \"what's the default again?\"; the resolved value is right there.\n\n`resetColumnVisibility$` (a `Trigger`, no payload) clears every override at once — typically wired to a \"Reset to defaults\" button. And there's a quieter convenience worth knowing: when the user toggles a column back to its declarative default, the override is dropped instead of stored as a redundant entry. \"Hide then show\" returns the table to a clean state, and the saved-state side of the feature stays minimal as a result.\n\n## Where the controls live\n\nThe demo above sits beside the table and connects through `engineRef`, using `useRemoteCellValue` and `useRemotePublisher`. That's the common shape — a toolbar, a preset menu, a settings panel — anywhere the controls aren't a child of `<DataTable>`.\n\nFor controls mounted _inside_ the table tree — a header slot, a footer, a context menu rendered from a cell — the simpler `useCellValue` and `usePublisher` hooks from `@virtuoso.dev/reactive-engine-react` work, picking up the same engine through React context:\n\n```tsx\nimport { columns$ } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\nimport { useCellValue, usePublisher } from '@virtuoso.dev/reactive-engine-react'\n\nfunction ColumnVisibilityPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div>\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key)\n        return (\n          <button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} type=\"button\">\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n```\n\n[Controlling the Table](/data-table/controlling-the-table/) covers the in-tree-vs-sibling distinction in depth.\n\n## How visibility composes with other column features\n\n- **Groups.** Hiding a column shrinks its `ColumnGroup` header band to whatever children remain visible. Hide every column in a group and the group header disappears with them — there's no empty band.\n- **Sticky.** A hidden sticky column simply vanishes; the columns next to it stay where they are rather than sliding into the pinned region.\n- **Reorder.** Visibility and order are independent — hiding a column doesn't change its position in `columns$`, and showing it again restores it to the slot it held before.\n\n## Saving the user's choice\n\n`columnVisibilityPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to save visibility across reloads. The saved shape is keyed by stable field name and contains only the _differences from declarative defaults_, so:\n\n- A user who hasn't touched the picker saves nothing.\n- A column that started visible and stays visible never appears in saved state.\n- Hiding a default-visible column writes one entry; un-hiding it drops the entry back out.\n\nThat makes the saved bundle forward-compatible: adding a new column to the JSX with a sensible declarative default doesn't surprise existing users with a stale override on next load. The catch sits on the other side — renaming a `field` invalidates the saved visibility for that field, since the saved state can no longer find the column it was about. The [field-name persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\nSee the [Column Visibility example](/data-table/examples/column-visibility/) for a runnable sibling-toolbar setup.\n","frontmatter":{"title":"Column Visibility","description":"Hide and restore columns with declarative defaults, reactive controls, and persistence.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":1857,"sizeBytes":8403},{"name":"06.column-resizing.md","path":"packages/data-table/docs/3.columns/06.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/06.column-resizing.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Resizing\ndescription: Let users change column widths with the opt-in resize model and shadcn resize handle.\nsidebar:\n  label: Column Resizing\n---\n\nA column's default width comes from its content and the table's layout — usually fine, sometimes not. Long product names need more room than the default; an ID column can shrink. Column resizing gives users a drag handle on the header edge to set the width themselves, and the table remembers their choice once persistence is wired up.\n\n## Adding the drag handle\n\nMount `ResizeHandle` from the shadcn `data-table-resize-handle` registry item inside a `HeaderEdge` slot. The handle owns the pointer interaction, the live preview while dragging, and the publish of the final width back to the table — the column declaration only has to expose the slot.\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-resize`.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 60 }, (_, index) => ({\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe slot alone is what enables resizing on a column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderEdge component={ResizeHandle} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nSkip `HeaderEdge` for columns the user shouldn't be allowed to resize — a fixed-width row-selector column, for example.\n\n## Where column widths live\n\nThe table owns every column's width through its header. It measures each `DataTableColumnHeader`, distributes any leftover viewport space across the visible columns, and uses the resulting size for every body cell in that column. The drag handle, the programmatic `resizeColumn$` call, and the persisted overrides feed into that same pipeline as user-selected rendered widths. While a resize override is active, sibling columns keep their current rendered widths so dragging a boundary feels direct and can create horizontal overflow.\n\nTo set a column's _base_ width, style the header with width or minimum-width classes. To decide which columns receive leftover horizontal space, use `DataTableColumn grow={number}`. Do not use Tailwind flex growth classes as the table sizing API:\n\n```tsx\n<DataTableColumn field=\"id\">\n  <DataTableColumnHeader className=\"w-20\">ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-48\">Product</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor the full sizing model, including fixed metadata columns, text-heavy grow columns, horizontal overflow, and resize override behavior, see [Column Layout](/data-table/columns/column-layout/).\n\n:::caution[Don't put widths on the cell]\nBody cells render inside a grid track sized to the column's measured width — they're already constrained from the outside. Width CSS on `DataTableCell` (`w-*`, `min-w-*`, `max-w-*`, `flex-*`, `basis-*`) doesn't influence the column and can fight the grid into clipped or overflowing content. When a column needs to be wider, widen its header. Reserve cell CSS for things inside the track: padding, alignment, typography, truncation.\n:::\n\nThis includes width pressure added for \"realistic\" content. If a prompt name, badge stack, action menu, or timestamp needs room, express that as a header width:\n\n```tsx\n<DataTableColumn field=\"updated_at\">\n  <DataTableColumnHeader className=\"min-w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror that width on the body cell. For multi-line or nested content, place layout helpers inside the cell renderer:\n\n```tsx\n<DataTableCell>\n  {({ row }) => (\n    <div className=\"min-w-0\">\n      <p className=\"truncate\">{row.data.name}</p>\n      <p className=\"truncate text-muted-foreground\">{row.data.slug}</p>\n    </div>\n  )}\n</DataTableCell>\n```\n\nAs a final review step, search for width utilities on cell declarations:\n\n```bash\nrg 'DataTableCell.*className=.*(w-|min-w|max-w|basis-|grow|shrink|flex-(none|auto|initial|1|\\[))'\n```\n\n## Resizing without the drag handle\n\nSome resizes don't originate from the column edge. A \"fit to content\" toolbar button, a \"Compact / Comfortable / Spacious\" preset, a keyboard shortcut — each needs to set the width without anyone dragging. Publish to `resizeColumn$` directly:\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n\nfunction fitNameColumn() {\n  const nameKey = [...(columns ?? new Map())].find(([, column]) => column.field === 'name')?.[0]\n  if (nameKey) {\n    resizeColumn({ key: nameKey, width: 280 })\n  }\n}\n```\n\nThe payload's `key` is the column's internal identifier, not its `field`. The lookup against `columns$` translates a stable, user-facing field name into the key the stream expects.\n\n## Persisting widths across reloads\n\nThe handle records the user's _intent_ — \"the user wants the name column 280px wide.\" Intent is what gets saved. Automatic `grow` output is not persisted; clearing the override lets the column fall back to its measured base width and the current grow distribution.\n\nMount `columnWidthPersistenceAdapter()` on `DataTableStatePersistence` to persist overrides. The adapter saves them keyed by `field`, so the saved entry survives reordering, partial column lists, and dynamically generated columns. [State Persistence](/data-table/state-persistence/) covers the full wiring, including the field-name contract that ties saved state to column declarations.\n\n## The full resize surface\n\nThree publishers cover the runtime mutations:\n\n- `resizeColumn$` — set a width override for one column.\n- `clearColumnWidthOverride$` — drop one override; the column falls back to its default.\n- `resetColumnWidthOverrides$` — drop every override at once, e.g. a \"Reset widths\" menu item.\n\nOne adapter wires those into persistence:\n\n- `columnWidthPersistenceAdapter()` — saves the override map; see [State Persistence](/data-table/state-persistence/).\n\nOne read-only cell exposes the realized layout, for layout-aware UI that needs the actual rendered widths:\n\n- `columnWidths$` — realized widths after distribution. Read for layout introspection; do **not** persist these values.\n\nThe [Column Resizing example](/data-table/examples/column-resizing/) puts the handle, preset menus, and persistence together in one table.\n","frontmatter":{"title":"Column Resizing","description":"Let users change column widths with the opt-in resize model and shadcn resize handle.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":1888,"sizeBytes":8432},{"name":"07.column-reordering.md","path":"packages/data-table/docs/3.columns/07.column-reordering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/07.column-reordering.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Reordering\ndescription: Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.\nsidebar:\n  label: Column Reordering\n---\n\nColumn reordering lets the user move columns into a different position than the JSX declares. The order in JSX becomes the initial order; the user changes it through a drag UI mounted in the header, a toolbar action, or programmatic engine calls.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 320 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n## Wiring the handles\n\nTwo pieces mount inside each column header: a draggable grip the user can grab, and a drop zone that paints the indicator line when another column is dragged over. They live in two different header slots so they don't fight each other for space — the grip sits in `HeaderStart` next to the title, and the drop indicator overlays the whole header through `HeaderOverlay` without disturbing layout.\n\n```tsx\nimport { HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\n;<DataTableColumn field=\"status\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Status'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n`ReorderGrip` and `ReorderDropZone` are shadcn registry components — drop them into a column header and reordering works. They're not the whole story, though: under them is an engine action any other surface can publish to.\n\n## Reordering from a toolbar or menu\n\nDrag isn't the only way to move columns. A \"Move to first\" menu item or an arrow-key shortcut can reach for `reorderColumns$` from outside the header. The shape is the same as what the drag UI publishes: source, target, and a `before` / `after` position.\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { reorderColumns$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst reorderColumns = useRemotePublisher(reorderColumns$, engineRef)\n\nconst entries = [...(columns ?? new Map())]\nconst statusKey = entries.find(([, column]) => column.field === 'status')?.[0]\nconst nameKey = entries.find(([, column]) => column.field === 'name')?.[0]\n\nif (statusKey && nameKey) {\n  reorderColumns({ sourceKey: statusKey, targetKey: nameKey, position: 'before' })\n}\n```\n\nThe two-step lookup is genuine API friction worth knowing: `reorderColumns$` operates on runtime _column keys_ (the identity the engine uses internally), not the `field` names you write in JSX. Most callers know the field — \"move 'status' before 'name'\" — and translate by walking `columns$` once. Column keys are stable for a given declaration, so for repeated programmatic moves it's worth building the field → key map once at mount and stashing it in a ref.\n\n## Resetting to declaration order\n\nFor a \"Reset to default order\" button, publish `resetColumnOrderToDeclaration$`. The table already tracks the current component declaration order internally, so the caller doesn't need to snapshot runtime column keys at mount.\n\n```tsx\nimport { useEngineRef, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnOrderToDeclaration$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst resetColumnOrder = useRemotePublisher(resetColumnOrderToDeclaration$, engineRef)\n\n;<button onClick={() => resetColumnOrder()} type=\"button\">\n  Reset order\n</button>\n```\n\nIf `columnOrderPersistenceAdapter()` is mounted, the reset is treated like any other order change and the declaration-order state is saved under the table's persistence key.\n\n## Sticky and group constraints\n\nTwo rules narrow what the drag UI accepts, and they apply equally to engine-level reorders:\n\n- **Sticky stays with sticky.** A drag from a `sticky=\"left\"` column only accepts drops on other left-sticky targets; right-sticky behaves the same. Non-sticky columns can't be pulled into a pinned region by the drag UI, and the engine action treats sticky-ness as a column property rather than a positional one — moving a non-sticky column next to a pinned one doesn't make it pinned.\n- **Group drag is its own gesture.** When columns live inside a `ColumnGroup`, the group header itself can be made draggable with `DraggableGroupHeader`, moving every member as one transaction. The single-column gesture stays available, but dragging an individual column across group boundaries breaks the visual band — see [Column Groups → Reordering with groups](/data-table/columns/column-groups/#reordering-with-groups) for the full picture and the practical mitigations.\n\n## Saving the user's order\n\n`columnOrderPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to keep the user's order across reloads. The saved shape is a list of `field` names — stable, declarative identifiers — rather than the runtime keys the engine uses internally. As a result:\n\n- Adding a new column to the JSX places it at its declared position on next load. Saved state doesn't list it, so the new column doesn't get a stale slot somewhere arbitrary.\n- Removing a column drops it from saved state without disturbing the others.\n- Renaming a `field` invalidates that entry's saved position — the saved state can't find the column it was about. [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\n## Building your own drag UI\n\nThe shadcn grip and drop zone are a reference implementation. If your design system uses a different drag library, you want long-press for touch, or the drop indicator needs custom positioning, the underlying coordination primitives are public:\n\n- `columnDragState$` — a reactive cell holding \"what's being dragged, where's the drop indicator.\" Grip writes, drop zone reads.\n- `beginColumnDrag$` / `endColumnDrag$` — lifecycle channels published from the grip's `dragstart` / `dragend`.\n- `setColumnDropTarget$` — what the drop zone publishes from `dragover`.\n\nThe source for `ReorderGrip` and `ReorderDropZone` in the shadcn registry is short and self-contained; copy it as a starting point and swap out the markup that doesn't fit.\n\nSee the [Column Reordering example](/data-table/examples/column-reorder/) for a runnable setup with persistence wired in.\n","frontmatter":{"title":"Column Reordering","description":"Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.","sidebar":{"label":"Column Reordering"}},"isInternal":false,"tokens":1822,"sizeBytes":7997},{"name":"08.runtime-columns.md","path":"packages/data-table/docs/3.columns/08.runtime-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/08.runtime-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Generating Columns at Runtime\ndescription: Discover and declare table columns when the field list is only known after data arrives.\nsidebar:\n  label: Runtime Columns\n---\n\nSome tables cannot name every column at build time. An admin viewer may render tenant-defined fields, a debug tool may inspect arbitrary JSON, or a report may receive its visible fields from the first data result.\n\n`DynamicColumns` is for that case. It waits for the first non-empty data result, calls your render function with that data, and lets you return normal `DataTableColumn` declarations. The columns stay fixed for the lifetime of that table instance — if the dataset shape changes later, remount the table with a new `key`.\n\n## What the render function receives\n\nThe render function is called with `{ data, model }`:\n\n- `data` is the first non-empty array the table captured. It does not change when the model later fetches more rows, filters, or fills in remote placeholders — you scan it once and produce columns from what's there.\n- `model` is the same handle you passed to `DataTable`, available for the rare case where a generated header or cell needs to publish a model action.\n\nField scanning lives in your app code so the rules — which fields to include, what counts as a sample value, how to handle group rows — stay obvious next to the table. A general-purpose starting point looks like this:\n\n```ts\ntype RuntimeField = {\n  field: string\n  sampleValue: unknown\n}\n\nfunction fieldsFromRows<Row>(rows: readonly Row[], includeField: (field: string) => boolean = () => true): RuntimeField[] {\n  const fields: RuntimeField[] = []\n  const fieldByName = new Map<string, RuntimeField>()\n  const hasSample = new Set<string>()\n\n  for (const row of rows) {\n    if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n      continue\n    }\n\n    for (const [field, value] of Object.entries(row)) {\n      if (!includeField(field)) {\n        continue\n      }\n\n      let descriptor = fieldByName.get(field)\n      if (!descriptor) {\n        descriptor = { field, sampleValue: value ?? null }\n        fieldByName.set(field, descriptor)\n        fields.push(descriptor)\n      }\n\n      if (!hasSample.has(field) && value !== null && value !== undefined) {\n        descriptor.sampleValue = value\n        hasSample.add(field)\n      }\n    }\n  }\n\n  return fields\n}\n```\n\nThat helper preserves first-seen field order and returns `{ field, sampleValue }` for each field. `sampleValue` is the first non-nullish value seen for that field, or `null` if the field exists but only has nullish values in the captured data.\n\n## Local data\n\nStatic columns declared around `DynamicColumns` keep their positions even though the runtime columns are produced between them. This example pins `id` on the left and `actions` on the right, and lets the table fill in everything else from the first local result.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { DynamicColumns, localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, region: 'US' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, region: 'EU' },\n]\n\ntype Product = (typeof rows)[number]\n\nconst model = localModel<Product>({ data: rows })\n\nfunction labelFor(field: string) {\n  return field.replaceAll('_', ' ').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nfunction fieldsToShow(rows: readonly Product[]) {\n  const fields: string[] = []\n  const seen = new Set<string>()\n\n  for (const row of rows) {\n    for (const field of Object.keys(row)) {\n      if (field !== 'id' && !seen.has(field)) {\n        seen.add(field)\n        fields.push(field)\n      }\n    }\n  }\n\n  return fields\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 300 }}>\n      <DataTableColumn field=\"id\" sticky=\"left\">\n        <DataTableColumnHeader className=\"w-24\">SKU</DataTableColumnHeader>\n        <DataTableCell className=\"text-muted-foreground tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      <DynamicColumns<Product>>\n        {({ data }) =>\n          fieldsToShow(data).map((field) => (\n            <DataTableColumn field={field} key={field}>\n              <DataTableColumnHeader className=\"min-w-32\">{labelFor(field)}</DataTableColumnHeader>\n              <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n            </DataTableColumn>\n          ))\n        }\n      </DynamicColumns>\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"w-24\">Actions</DataTableColumnHeader>\n        <DataTableCell>{({ row }) => <button type=\"button\">Open {row.data.id}</button>}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe `key={field}` on each generated column matters. Column state such as width, order, visibility, and sticky placement is tied to the field identity.\n\n## Remote data\n\nRemote tables derive their schema from the first non-empty result the same way local tables do. The descriptor helper above is reusable — the `sampleValue` lets the render prop pick alignment per field:\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"id\" sticky=\"left\" />\n\n  <DynamicColumns<ProductRow>>\n    {({ data }) =>\n      fieldsFromRows(data, (field) => field !== 'id').map(({ field, sampleValue }) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>{labelFor(field)}</DataTableColumnHeader>\n          <DataTableCell {...(typeof sampleValue === 'number' ? { className: 'text-right tabular-nums' } : {})}>\n            {({ cellValue }) => String(cellValue ?? '')}\n          </DataTableCell>\n        </DataTableColumn>\n      ))\n    }\n  </DynamicColumns>\n\n  <DataTableColumn id=\"actions\" sticky=\"right\" />\n</DataTable>\n```\n\nThe headers appear after the first non-empty result resolves. Later pages or viewport fetches do not add more columns — that avoids mid-scroll layout changes, but it means the first captured result must contain every field you want to show.\n\nWhen the backend can return column metadata alongside the rows, prefer that. Metadata can render headers before any rows arrive, works for empty responses, and can carry labels, alignment, and visibility hints out of band.\n\n## Placeholders\n\nThe field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data. Placeholder-only fields show up in the discovered column list, and fields that exist only on real rows go missing if the first captured result is all placeholders.\n\nMake placeholders schema-compatible with real rows:\n\n```ts\nconst placeholder = {\n  id: '',\n  product: '',\n  category: '',\n  stock: null,\n}\n```\n\nDo not put placeholder-only fields in the visible schema. If a placeholder needs technical fields for rendering, filter them with `includeField`. If the real field list cannot be known until the backend responds, return column metadata from the backend or wait for a real data result instead of relying on placeholder shape.\n\n## Grouped data\n\nGrouped models mix group-header rows and body rows in the same array. The field scanner treats them the same way, so any field on a group-header row (a `groupLabel`, a level number, a count) would become a body column unless you filter group rows out first:\n\n```ts\nconst fields = fieldsFromRows(\n  data.filter((row) => !(typeof row === 'object' && row !== null && 'groupLabel' in row)),\n  (field) => field !== 'id'\n)\n```\n\nRender group labels with `DataTableGroupHeader`; keep runtime columns focused on ordinary body rows.\n\n## Rediscovering columns\n\n`DynamicColumns` is intentionally one-shot. A user action that changes row values should not reshuffle the table's columns.\n\nWhen a user intentionally switches to a different dataset shape, remount the table:\n\n```tsx\n<DataTable key={datasetId} model={model}>\n  <DynamicColumns>{/* declare columns from the new first result */}</DynamicColumns>\n</DataTable>\n```\n\nA user-facing column-management UI — letting the user add, remove, or reorder fields after mount — is a different problem. Keep the field list in your own React state and map it to `DataTableColumn` declarations directly; `DynamicColumns` doesn't help here, and trying to bend it to that shape leads to the temporal bugs the one-shot rule was designed to avoid.\n\n## When this does not fit\n\n- **Backend-defined schemas.** Return column metadata alongside the rows when headers must render before data, when valid responses can be empty, or when labels and alignment come from server configuration.\n- **Statically-known schemas.** Use a static field list when the schema is known in code but the JSX would be repetitive — see [Generated columns](/data-table/columns/defining-columns/#generated-columns).\n- **Runtime column-management UI.** Lift the field list into your own React state when the user can add, remove, or reorder fields after mount.\n\n`DynamicColumns` fits when the schema is data-defined, the first non-empty result is representative, and remounting the table is acceptable for re-discovery.\n","frontmatter":{"title":"Generating Columns at Runtime","description":"Discover and declare table columns when the field list is only known after data arrives.","sidebar":{"label":"Runtime Columns"}},"isInternal":false,"tokens":2071,"sizeBytes":9389},{"name":"09.column-layout.md","path":"packages/data-table/docs/3.columns/09.column-layout.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/09.column-layout.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Layout\ndescription: Set base column widths and choose which columns grow into extra horizontal space.\nsidebar:\n  label: Column Layout\n---\n\nColumn width has two parts: the base width and the realized width.\n\nThe base width comes from the column header. The table measures `DataTableColumnHeader`, then uses that measured width as the starting point for both the header and every body cell in that column.\n\nThe realized width is the width the column gets after the table compares all base widths with the viewport. When the viewport is wider than the total base width, extra space is distributed by the table. When the viewport is narrower, columns keep their base widths and the table scrolls horizontally.\n\n## Set Base Widths On Headers\n\nUse header width classes for the minimum useful size of each column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror those widths on `DataTableCell`. Body cells render inside tracks sized by the table. Cell width utilities can fight those tracks and cause clipping or overlap.\n\n## Choose Which Columns Grow\n\nUse `DataTableColumn grow={number}` for columns that should absorb extra viewport width. The value is a ratio, not a CSS flex shorthand.\nOmitting `grow`, or passing `grow={0}`, keeps the column fixed when another visible column grows.\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"description\" grow={3}>\n  <DataTableColumnHeader className=\"min-w-80\">Description</DataTableColumnHeader>\n  <DataTableCell>{DescriptionCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"versions\">\n  <DataTableColumnHeader className=\"w-40\">Versions</DataTableColumnHeader>\n  <DataTableCell>{VersionBadgesCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn id=\"actions\">\n  <DataTableColumnHeader className=\"w-16 justify-center\">Actions</DataTableColumnHeader>\n  <DataTableCell>{ActionsCell}</DataTableCell>\n</DataTableColumn>\n```\n\nIn this layout, `name` and `description` receive all extra width. `description` receives three times as much extra width as `name`. `versions`, `updated`, and `actions` keep their base widths.\n\nChoose grow columns from the local data and workflow. IDs, slugs, checkboxes, action menus, short labels, badges, version counts, timestamps, and compact metadata usually should not grow. Names, titles, descriptions, summaries, messages, notes, paths, and other text-heavy content usually benefit from extra room. The field name is only a clue: a short code-like `name` may be fixed, while a user-authored label column may deserve room.\n\nDo not make every column grow. The point of `grow` is to let useful text columns breathe while compact columns stay compact.\n\n## What Happens Without Grow\n\nTables that do not set `grow` preserve the default auto-fill behavior: when the viewport is wider than the total base width, extra space is distributed evenly across visible columns. This keeps existing tables filling their container without changing every column declaration.\n\nOnce any visible column has a positive finite `grow` value, only grow columns receive leftover width. Non-grow columns keep their base widths.\n\n## There Is No Shrink Prop\n\n`grow` only distributes extra space. It does not shrink columns below their base widths, and there is no `shrink` table API.\n\nWhen the viewport narrows, grow space disappears first. After the total base width no longer fits, the table keeps base widths and uses horizontal scrolling.\n\n## Resizing Grow Columns\n\nColumn resizing sets the rendered width of that column. During and after a resize override, the table preserves the current rendered widths of the other columns instead of redistributing grow space on every pointer move. That keeps the resize handle under the pointer and lets wider columns push the table into horizontal overflow.\n\nClear the resize override to return the column to its measured base width and the normal `grow` distribution.\n\nPersist resize overrides as user-selected widths, not as automatic grow output. The saved width is the user's explicit override; automatic grow space is recalculated only when no resize override is active for that layout.\n","frontmatter":{"title":"Column Layout","description":"Set base column widths and choose which columns grow into extra horizontal space.","sidebar":{"label":"Column Layout"}},"isInternal":false,"tokens":988,"sizeBytes":4773},{"name":"index.md","path":"packages/data-table/docs/3.columns/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/3.columns/index.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Overview\ndescription: Define the visible columns, headers, and cells that turn model rows into a grid.\nsidebar:\n  order: 0\n  label: Overview\n---\n\nColumns decide which fields are visible, in what order, at what width, and how each cell is rendered. You declare them as JSX children of `DataTable` — one `DataTableColumn` per visible field, with its header and cell renderer inside.\n\nThe JSX reads like a static description of the table, but `DataTableColumn` and its children don't render in place. Each one registers a definition with the table, which then draws the virtualized header row and the visible body cells from those definitions. That indirection is what makes virtualization possible: a single cell renderer is reused across many row positions as the user scrolls, and headers compose cleanly with sticky pinning, resizing, slots, and visibility.\n\n## Basic columns\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium\">{row.data.name}</span>\n              <span className=\"text-xs text-muted-foreground\">{row.data.id}</span>\n            </div>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : 'Healthy'}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What each part defines\n\n- `DataTableColumn` — one column, keyed by `field`.\n- `DataTableColumnHeader` — header content and header class.\n- `DataTableCell` — body-cell renderer and cell class.\n\nOmit the header to fall back to the field name; omit the cell to fall back to the field value.\n\n`field` is both the cell lookup key (`row.data[field]`) and the column's persistent identity. Keep it stable across releases if you use any column features that save state — see [State Persistence](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the rules around renaming.\n\nFor layout, put base widths on `DataTableColumnHeader` and use `DataTableColumn grow={...}` only for columns that should absorb extra horizontal space. See [Column Layout](/data-table/columns/column-layout/) for the sizing model and guidance on choosing fixed vs growing columns from the data being displayed.\n\n## Where to go next\n\nDeclarative shape:\n\n- [Defining Columns](/data-table/columns/defining-columns/) — `field`, default rendering, declaration order, visibility, generated columns.\n- [Formatting Cells and Headers](/data-table/columns/cell-and-header-renderers/) — render params, extracted renderer functions, custom header content.\n- [Column Layout](/data-table/columns/column-layout/) — base widths, `grow` ratios, fixed metadata columns, and horizontal overflow.\n- [Column Groups](/data-table/columns/column-groups/) — wrap adjacent columns under a shared header.\n- [Sticky Columns](/data-table/columns/sticky-columns/) — pin a column to the left or right edge during horizontal scrolling.\n\nInteractive layout:\n\n- [Column Visibility](/data-table/columns/column-visibility/) — declarative defaults, runtime toggling, \"Columns\" picker.\n- [Column Resizing](/data-table/columns/column-resizing/) — drag handle in the header, presets, auto-fit.\n- [Column Reordering](/data-table/columns/column-reordering/) — drag-and-drop individual columns or whole groups.\n\nDynamic schemas:\n\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n","frontmatter":{"title":"Overview","description":"Define the visible columns, headers, and cells that turn model rows into a grid.","sidebar":{"order":0,"label":"Overview"}},"isInternal":false,"tokens":1096,"sizeBytes":4897},{"name":"4.grouped-rows.md","path":"packages/data-table/docs/4.grouped-rows.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/4.grouped-rows.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Grouped Rows\ndescription: Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.\nsidebar:\n  label: Grouped Rows\n---\n\nWhen records belong to natural sections — products by category, invoices by customer, employees by department — group headers split the scrolling row stream into visible groups. Users see one continuous list; the headers anchor to the top of each section as new rows scroll into view.\n\n(This page is about row-level grouping. For grouping in the column header row, see [column groups](/data-table/columns/column-groups/).)\n\n## Letting users change groups\n\nThe common case: a \"Group by\" control where the user picks the grouping at runtime. Click the buttons in this example to regroup the table:\n\n```tsx live\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ninterface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  status: 'In stock' | 'Low stock'\n}\n\ninterface ProductGroup {\n  label: string\n}\n\ntype GroupBy = 'none' | 'category' | 'status'\n\nconst products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', status: 'In stock' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office', status: 'Low stock' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-004', name: 'Mechanical Keyboard', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-005', name: 'Studio Monitor', category: 'Audio', status: 'Low stock' },\n  { id: 'SKU-006', name: 'Audio Interface', category: 'Audio', status: 'In stock' },\n]\n\nfunction isProduct(row: Product | ProductGroup): row is Product {\n  return 'id' in row\n}\n\nfunction rowKey(row: Product | ProductGroup) {\n  return isProduct(row) ? row.id : `group-${row.label}`\n}\n\nfunction groupProducts(rows: Product[], groupBy: GroupBy) {\n  if (groupBy === 'none') {\n    return rows\n  }\n\n  const buckets = new Map<string, Product[]>()\n  for (const row of rows) {\n    const key = groupBy === 'category' ? row.category : row.status\n    const bucket = buckets.get(key)\n    if (bucket) {\n      bucket.push(row)\n    } else {\n      buckets.set(key, [row])\n    }\n  }\n\n  const data: (Product | ProductGroup)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const [key, items] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${key} (${items.length})` })\n    data.push(...items)\n  }\n\n  return { data, groups }\n}\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = (actionState?.group?.payload ?? 'category') as GroupBy\n\n  const options: { value: GroupBy; label: string }[] = [\n    { value: 'none', label: 'No groups' },\n    { value: 'category', label: 'Group by category' },\n    { value: 'status', label: 'Group by status' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <button\n          aria-pressed={groupBy === option.value}\n          className={`rounded-md border px-3 py-1 text-sm ${groupBy === option.value ? 'bg-foreground text-background' : 'bg-background'}`}\n          key={option.value}\n          onClick={() => dispatch({ action: 'group', payload: option.value })}\n        >\n          {option.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['group'],\n      initialActions: [{ action: 'group', payload: 'category' }],\n      actions: {\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <GroupByControls engineRef={engineRef} />\n      <DataTable\n        className=\"rounded-xl\"\n        computeRowKey={({ data }) => rowKey(data as Product | ProductGroup)}\n        engineRef={engineRef}\n        model={model}\n        style={{ height: 320 }}\n      >\n        <GroupHeaderCell className=\"bg-muted/60 px-3 py-2 text-sm font-medium\">\n          {({ row }) => String((row.data as ProductGroup).label)}\n        </GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nThe interactive grouping above does three things:\n\n- **Declares a `group` pipeline stage** on the model so the table knows where in its data pipeline regrouping runs. The pipeline lists the stages in order; `['group']` is the simplest case, but `['filter', 'sort', 'group']` is the typical production shape.\n- **Defines a `group` action** whose handler receives the current data and a payload (the user's selection) and returns the new `{ data, groups }` shape.\n- **Connects the controls through the table engine.** The buttons publish to `dispatchModelAction$`; the active button reads `modelActionState$`. The toolbar never stores a second copy of the grouping state.\n\nThe wire-up, isolated from the demo's rendering code, looks like this:\n\n```tsx\nconst [model] = useState(() =>\n  localModel<Product, ProductGroup>({\n    data: products,\n    pipeline: ['group'],\n    initialActions: [{ action: 'group', payload: 'category' }],\n    actions: {\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n      },\n    },\n  })\n)\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = actionState?.group?.payload ?? 'category'\n\n  return (\n    <button aria-pressed={groupBy === 'category'} onClick={() => dispatch({ action: 'group', payload: 'category' })}>\n      Group by category\n    </button>\n  )\n}\n```\n\nFor the full filter + sort + group combination — three controls sharing one pipeline — see [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for the runnable end-to-end recipe.\n\n## Pre-grouped data\n\nWhen the grouping is fixed at mount and won't change at runtime, skip the pipeline action and pass the already-flattened result directly:\n\n```tsx\nconst tableData = {\n  data: [\n    { label: 'Office (2)' },\n    { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n    { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  ],\n  groups: [{ index: 0, level: 0 }],\n}\n\nconst model = localModel(tableData)\n```\n\n`GroupHeaderCell` receives `row.data` for the marked entry, so it can read whatever fields you put on the group object (here, `label`).\n\n## How grouping is expressed\n\nThe table doesn't accept nested children. It receives one flattened row array plus markers identifying which entries are group headers:\n\n```ts\ntype GroupedRows<TData, TGroup> = {\n  data: Array<TData | TGroup>\n  groups: { index: number; level: number }[]\n}\n```\n\n- `data` mixes group rows and data rows in display order.\n- `groups[]` marks which positions in `data` are headers. Each marker carries an `index` (zero-based position in the flattened array) and a `level` (nesting depth).\n- The table renders marked entries through `GroupHeaderCell` instead of through column cells.\n\nGroup rows only need the fields your `GroupHeaderCell` reads. Data rows need the fields your columns read. The two shapes can differ; the table picks the renderer per entry based on the markers.\n\n## Multi-level grouping\n\nFor deeper hierarchy (region → category → product), use sequential markers with increasing levels:\n\n```ts\nconst groups = [\n  { index: 0, level: 0 }, // 'North America'\n  { index: 1, level: 1 }, //   'Office'\n  { index: 4, level: 1 }, //   'Peripherals'\n  { index: 7, level: 0 }, // 'Europe'\n  { index: 8, level: 1 }, //   'Office'\n]\n```\n\nHierarchy is implicit in row order: every level-1 marker belongs to the most recent level-0 marker that precedes it. Style nesting visually by branching on `row.level` inside `GroupHeaderCell`:\n\n```tsx\n<GroupHeaderCell>\n  {({ row }) => (\n    <div className={row.level === 0 ? 'bg-muted font-medium' : 'bg-muted/40 pl-6'}>{(row.data as { label: string }).label}</div>\n  )}\n</GroupHeaderCell>\n```\n\n`level` controls visual and sticky nesting only — it does not add parent-child data behavior. If you need to know which level-0 group a row belongs to, derive it from row order at the time you flatten.\n\n## Remote grouped data\n\nWhen the grouping is computed on the backend, the fetch result includes markers alongside the rows; `remoteModel` forwards them to the table. The backend returns a flattened grouped result, not nested children:\n\n```ts\nasync function fetchProducts(params: FetchParams<Query>) {\n  const result = await fetchGroupedRows(params)\n\n  return {\n    rows: result.rows,\n    groups: result.groups,\n    totalCount: result.totalCount,\n  }\n}\n```\n\nIn offset mode, `totalCount` is the length of the full flattened result (including group headers), and marker indexes are positions in that full result — not relative to the current page. In append mode, return markers for the row stream after each append. Group rows still need a shape your `GroupHeaderCell` can branch on.\n\n## Rules of thumb\n\n- Group rows are virtualized full-width rows — keep their content lightweight.\n- Give group rows stable keys via `computeRowKey` when grouping, sorting, or filtering can reorder them. Use a stable group identifier (`groupKey`, `region`, etc.), not the current group index.\n- The table doesn't compute groups for you. Either group your data before passing it in, or compute inside a pipeline stage.\n- `level` controls visual and sticky nesting. It does not add parent-child data behavior.\n\n## When this doesn't fit\n\n- **Nested source data.** Flatten it before passing to the table — the table's display contract is flat-with-markers, not nested children.\n- **User-toggleable expand/collapse.** Keep the expanded/collapsed state in your app and rebuild the flattened `data` and `groups` from it on each toggle.\n- **Remote grouping across unloaded ranges.** Compute the grouping on the backend, or return enough metadata for the client to place group headers correctly without seeing every row.\n\nSee [Group Rows by Region and Category](/data-table/examples/grouped-data/) for a complete pre-grouped local table, and [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for grouping wired alongside filter and sort.\n","frontmatter":{"title":"Grouped Rows","description":"Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":2792,"sizeBytes":11637},{"name":"5.state-persistence.md","path":"packages/data-table/docs/5.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/5.state-persistence.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: State Persistence\ndescription: Persist table feature state and model action state with opt-in adapters.\nsidebar:\n  label: State Persistence\n---\n\n`DataTableStatePersistence` saves column width, order, visibility, and model action state under a storage key, restoring them on mount. With it wired up, a user's resize, hide, drag, or filter survives a reload. Two requirements: mount the component inside the table, and pass an adapter for each feature you want persisted.\n\n## Minimal setup\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columns$, localModel, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport {\n  columnVisibilityPersistenceAdapter,\n  columnVisibilityState$,\n  setColumnVisibility$,\n} from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence } from '@virtuoso.dev/data-table/state-persistence'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n  { id: 'SKU-004', product: 'Noise-canceling Headset', category: 'Audio', stock: 9, internalId: 'inv-004' },\n]\n\nconst model = localModel({ data: rows })\nconst adapters = [columnVisibilityPersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nfunction ColumnPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div className=\"mb-3 flex flex-wrap gap-2 rounded-md border bg-muted/30 p-3 text-sm\">\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key) ?? column.visible !== false\n        return (\n          <button\n            aria-pressed={visible}\n            className={visible ? 'rounded border bg-background px-2 py-1 font-medium' : 'rounded border px-2 py-1 text-muted-foreground'}\n            key={key}\n            onClick={() => setColumnVisibility({ key, visible: !visible })}\n            type=\"button\"\n          >\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 360 }}>\n      <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-feature-page\" />\n      <ColumnPicker />\n\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"internalId\" visible={false}>\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Internal ID'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nReload after hiding a column or resizing a header — the adapters you passed restore that state.\n\n## Adapters\n\n| Adapter                                | Subpath              | Persists                                                                                 |\n| -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |\n| `modelStatePersistenceAdapter()`       | `/state-persistence` | Action payloads from `localModel()` / `remoteModel()` (filters, sort, group)             |\n| `columnOrderPersistenceAdapter()`      | `/column-reorder`    | Column order, keyed by field                                                             |\n| `columnWidthPersistenceAdapter()`      | `/column-resize`     | Width overrides, keyed by field                                                          |\n| `columnVisibilityPersistenceAdapter()` | `/column-visibility` | Visibility overrides (only those differing from the declarative default), keyed by field |\n\n```tsx\nimport { columnOrderPersistenceAdapter } from '@virtuoso.dev/data-table/column-reorder'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { columnVisibilityPersistenceAdapter } from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nconst adapters = [\n  columnVisibilityPersistenceAdapter(),\n  columnOrderPersistenceAdapter(),\n  columnWidthPersistenceAdapter(),\n  modelStatePersistenceAdapter(),\n]\n\n<DataTable model={model} style={{ height: 420 }}>\n  <DataTableStatePersistence adapters={adapters} storageKey=\"inventory-table\" />\n  {/* columns */}\n</DataTable>\n```\n\nStorage defaults to `window.localStorage`. Pass `storage` for `sessionStorage`, a test double, or an application-specific backend.\n\nThe `adapters` array must be stable — define it outside the component or wrap it in `useMemo()` so the persistence component doesn't resubscribe on every render.\n\n## Storage keys and resets\n\n`storageKey` identifies one logical table. Change it when the saved state should no longer apply (different product surface, breaking schema change).\n\n`resetKey` clears saved state without changing the identity — bump it when the user explicitly resets a view.\n\n```tsx\n<DataTableStatePersistence adapters={adapters} resetKey={resetVersion} storageKey=\"inventory-table\" />\n```\n\nAdapters preserve unmatched entries where they can, so state survives partially-matching dynamic schemas.\n\n## Field names are the persistence contract\n\nThe adapters above all key their saved state by `field`. A column's width entry is \"the width for the column whose `field` is `'price'`\", not \"the width for the column in position 2\". Same for order, visibility, and sticky pin. That's what makes saved state survive schema migrations, partial column lists, and dynamic column generation — the field name is the contract between the column declaration and the saved entry.\n\nTwo consequences fall out of that:\n\n**Don't rename `field` lightly.** A column declared as `field=\"price\"` last week and `field=\"cost\"` this week is two different columns from persistence's perspective. The width entry under `'price'` doesn't transfer to `'cost'`, and the user's saved width silently goes back to the default. For schema migrations that have to rename, plan a one-time migration step that rewrites saved entries from the old key to the new one.\n\n**Keep `field` separate from header copy.** Header text is presentation and changes freely (locale switches, A/B tests, restyling). `field` is identity and must stay the same:\n\n```tsx\n<DataTableColumn field=\"customerName\">\n  <DataTableColumnHeader>{locale === 'de' ? 'Kunde' : 'Customer'}</DataTableColumnHeader>\n</DataTableColumn>\n```\n\nThis separation is why persisted state survives locale changes — the saved width sits under `'customerName'` regardless of what the header reads in any given language.\n\n## Model action persistence\n\nOnly actions marked with `persistence` are saved. Add it to a pipeline action for local models, or to an action that transforms request params for remote models.\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { status: 'all' },\n  actions: {\n    filter: {\n      persistence: { isEmpty: (value) => value === 'all', key: 'status' },\n      handler: ({ params, payload }) => ({ ...params, status: payload }),\n    },\n  },\n})\n```\n\nThe adapter saves action payloads, not row data. On restore, the model replays the action and rebuilds the request or local pipeline result.\n\nSee the [State Persistence example](/data-table/examples/state-persistence/) for a remote table wiring model and column-width persistence together.\n","frontmatter":{"title":"State Persistence","description":"Persist table feature state and model action state with opt-in adapters.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1898,"sizeBytes":8940},{"name":"6.controlling-the-table.md","path":"packages/data-table/docs/6.controlling-the-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/6.controlling-the-table.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Controlling the Table\ndescription: Read table state and publish table actions from nearby toolbars, panels, and other application UI.\nsidebar:\n  label: Controlling the Table\n---\n\nExternal UI — toolbars, settings menus, status badges — often needs to read the table's state (current scroll position, visible columns, loading phase) and trigger its actions (scroll to a row, toggle a column, refresh data). The table exposes both through its internal reactive runtime; external UI taps that runtime directly instead of receiving lifted state through props.\n\nTwo pieces wire the connection. The first is an identifier for the table instance: `useEngineRef()` returns a ref for UI in the same component, and a string `engineId` reaches a table mounted elsewhere in the tree. The second is the channels the identifier carries — _state cells_ like `scrollLocation$` and `columns$` expose values you read with `useRemoteCellValue`, and _action streams_ like `scrollToRow$` and `setColumnVisibility$` accept commands you publish with `useRemotePublisher`. The `$` suffix marks both flavors.\n\n## Engine refs\n\nReach for `engineRef` when the external UI lives in the same component as the table — a toolbar two divs up, a status panel rendered as a sibling. `useEngineRef()` returns an opaque ref; pass it to `<DataTable engineRef={engineRef}>` and to the remote hooks beside it, and they resolve to the same engine.\n\n```tsx live\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, scrollLocation$, scrollToRow$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 80, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 81\n        </Button>\n      </div>\n\n      <div className=\"rounded-lg border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n        {scrollLocation === undefined ? 'Waiting for scroll state...' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 360 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nMount order doesn't matter. The hooks register against the ref immediately and start receiving values as soon as the table's engine comes up.\n\n## Engine IDs\n\nReach for `engineId` when the external UI lives _far_ from the table — a page-level toolbar in the layout shell, a sibling panel that doesn't share a parent component with the table, anything where threading a ref through props would mean prop-drilling through unrelated levels. Pick a stable string, pass it as `<DataTable engineId={…}>`, and hand the same string to the hooks anywhere in the tree.\n\n```tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { scrollToRow$, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst TABLE_ENGINE_ID = 'inventory-table'\n\nfunction PageToolbar() {\n  const scrollToRow = useRemotePublisher(scrollToRow$, TABLE_ENGINE_ID)\n\n  return (\n    <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n      Top\n    </Button>\n  )\n}\n\nfunction InventoryTable({ rows }) {\n  return (\n    <DataTable model={model} engineId={TABLE_ENGINE_ID} style={{ height: 420 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nUse a unique ID per table on a given screen (`inventory-table`, `transactions-table`). The registry holds one engine per ID, so a second table mounting with the same `engineId` silently overwrites the first — and when the first unmounts, the registry entry is cleared out from under the second. The symptoms are confusing; the prevention is trivial.\n\n## Firing model actions from external UI\n\nFiltering, sorting, grouping, and search often live in a toolbar beside the table. Keep that toolbar connected to the model through the table engine: publish the user action with `dispatchModelAction$`, then read `modelActionState$` to decide which control is active. The toolbar does not need the model handle, and it does not need to keep a separate React state value in sync with the model.\n\n```tsx live\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ntype StatusFilter = 'all' | 'active' | 'paused'\n\nconst rows = [\n  { id: 'acct-1', account: 'Northwind', status: 'active' },\n  { id: 'acct-2', account: 'Contoso', status: 'paused' },\n  { id: 'acct-3', account: 'Fabrikam', status: 'active' },\n]\n\nfunction filterRows(data: typeof rows, status: StatusFilter) {\n  return status === 'all' ? data : data.filter((row) => row.status === status)\n}\n\nfunction StatusControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const selected = (actionState?.status?.payload ?? 'all') as StatusFilter\n\n  const options: { value: StatusFilter; label: string }[] = [\n    { value: 'all', label: 'All' },\n    { value: 'active', label: 'Active' },\n    { value: 'paused', label: 'Paused' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <Button\n          aria-pressed={selected === option.value}\n          key={option.value}\n          onClick={() => dispatch({ action: 'status', payload: option.value })}\n          variant={selected === option.value ? 'default' : 'outline'}\n        >\n          {option.label}\n        </Button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel({\n      data: rows,\n      pipeline: ['status'],\n      initialActions: [{ action: 'status', payload: 'all' }],\n      actions: {\n        status: {\n          stage: 'status',\n          handler: ({ data, payload }) => filterRows(data, payload as StatusFilter),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <StatusControls engineRef={engineRef} />\n      <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} engineRef={engineRef} model={model} style={{ height: 280 }}>\n        <DataTableColumn field=\"account\">\n          <DataTableColumnHeader>Account</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n`modelActionState$` tracks stateful model actions: the current filter, sort, group, search, or other action payload that represents a user choice. Command-like actions are different. `refresh`, `cancel`, `loadMore`, source mutators, and similar commands can be dispatched through `dispatchModelAction$`, but they are not remembered in `modelActionState$` because they do not describe the current toolbar state.\n\nThe bridge blocks only model lifecycle names owned by the table itself: `handshake`, `disconnect`, and `viewportChange`.\n\n## Common state and actions\n\nThe cells and streams most external UI reaches for, grouped by the hook they pair with.\n\nRead with `useRemoteCellValue`:\n\n- `columns$`, `visibleColumns$`, `columnWidths$` — column shape and layout\n- `scrollLocation$`, `scrollerElement$`, `itemHeight$` — scroll state and DOM references\n\nPublish with `useRemotePublisher`:\n\n- `scrollToRow$`, `scrollIntoView$`, `cancelSmoothScroll$` — scroll commands\n- `setColumnSticky$` — pin a column at runtime\n\nFeature modules contribute their own: `resizeColumn$` and friends from `@virtuoso.dev/data-table/column-resize`, `reorderColumns$` from `column-reorder`, `setColumnVisibility$` from `column-visibility`. Each feature page documents the cells and streams it adds.\n\nInside the table — in a cell renderer, in a custom row component — use the local hooks instead: `useVirtuosoLocation()`, `useCurrentlyRenderedData()`, `useVirtuosoLoadingState()`. They read the same underlying state but resolve the engine from React context, so no ref or ID is needed.\n\nFor DOM-level integrations, prefer `scrollerElement$` and `itemHeight$` over `document.querySelector`. The cells stay in sync with React's mount and unmount; a query selector can return a node from a torn-down render or miss a remount entirely.\n\n## Scroll lifecycle props\n\nOne-way notifications and first-render positioning live on the component, not the engine:\n\n- `initialLocation` — scrolls to a row when the table first renders with data\n- `onScroll` — receives the current `ListScrollLocation`\n- `onRenderedDataChange` — receives the rendered data rows, excluding group rows\n\n```tsx\n<DataTable\n  model={model}\n  initialLocation={{ index: 80, align: 'center' }}\n  onRenderedDataChange={(renderedRows) => {\n    console.log(renderedRows.map((row) => row.id))\n  }}\n  onScroll={(location) => {\n    console.log(location.scrollTop)\n  }}\n>\n  {/* columns */}\n</DataTable>\n```\n\nUse props when the parent only needs to observe — log scroll position, fire analytics, set an initial location once. Reach for the remote hooks when UI elsewhere in the tree needs to subscribe to the same updates or push commands back; props can't carry a subscription out to a sibling component.\n\n## Overscan controls\n\nA pair of static-tuning props that, like the scroll lifecycle props above, live on the component rather than on the engine — overscan is a per-table policy, not something external UI flips at runtime. The defaults are good for typical row heights and cell complexity; raise them only when you can see the table struggling.\n\n- `increaseViewportBy` — extra vertical pixels rendered before and after the viewport. Raise when rows contain images that need to warm up before they scroll into view, or when each row's render cost is high enough that the user can outrun the scheduler.\n- `columnOverscanCount` — extra columns rendered on each side of the horizontal viewport. Raise on wide tables that feel jumpy during horizontal scroll, especially when cells contain charts or non-trivial markup.\n\nBoth numbers cost render work even when the user isn't scrolling, so bump them in small steps and stop as soon as the symptom is gone.\n","frontmatter":{"title":"Controlling the Table","description":"Read table state and publish table actions from nearby toolbars, panels, and other application UI.","sidebar":{"label":"Controlling the Table"}},"isInternal":false,"tokens":2775,"sizeBytes":12134},{"name":"01.styling.md","path":"packages/data-table/docs/7.customization/01.styling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/01.styling.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Styling Cells, Headers, and Rows\ndescription: Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.\nsidebar:\n  label: Styling\n---\n\nThe visible parts of the table — cells, headers, the outer frame — all accept Tailwind classes the way any component in your app does. Reach for column-level styling when a single column needs to look different (a status pill, a right-aligned number, a muted secondary line) and wrapper-level styling when the change applies to the whole table.\n\n## Column-level styling\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', status: 'Launch ready', theme: 'Office' },\n  { name: 'USB-C Dock', status: 'Low stock', theme: 'Peripherals' },\n  { name: 'Noise-canceling Headset', status: 'Backorder', theme: 'Audio' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"theme\">\n        <DataTableColumnHeader>Theme</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-slate-100 px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => (\n            <span\n              className={\n                cellValue === 'Backorder'\n                  ? 'font-medium text-red-600'\n                  : cellValue === 'Low stock'\n                    ? 'font-medium text-amber-600'\n                    : 'font-medium text-emerald-600'\n              }\n            >\n              {String(cellValue)}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n`className` on `DataTableCell` styles the positioned cell. A column header has two widths, so it exposes two styling surfaces:\n\n- `DataTableColumnHeader.className` styles the intrinsic header content and sets the column's base width.\n- `DataTableColumnHeader.containerProps` styles the full rendered column width and accepts event handlers and ARIA attributes.\n\nThe table measures the intrinsic surface before it distributes spare width through `grow`. Measuring the rendered surface instead would feed the grown width back in as the next base width, so the column could not shrink with its viewport. `containerProps` provides a separate full-width paint and interaction surface without changing that measurement.\n\nUse `containerProps` when a background, hover state, outline, box shadow, cursor, or click target must cover the spare width:\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-64\" containerProps={{ className: 'cursor-pointer hover:bg-muted/50' }}>\n    Product\n  </DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nKeep layout on `className`. The table owns the outer container's geometry, so width, flex, margin, padding, border, display, and `justify-*` styles on `containerProps` can clip content or move edge-aligned slots. Use `box-shadow` or `outline` instead of a border on that surface, and use `HeaderEnd` for controls that must align to the rendered edge.\n\nContainer events receive bubbled events from nested controls and header slots. If the header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nRender-function children still let you wrap only the value when it needs its own markup, such as a badge or two-line label.\n\n## Wrapper-level styling\n\n`className` on `<DataTable>` reaches the outer frame around the scroll viewport. The shadcn wrapper already provides the default frame (`rounded-md border`) and table surface, so you do not need to repeat `rounded-md border` at each instance. Use wrapper classes for intentional frame overrides, shadows, and table-level CSS variable overrides.\n\n```tsx\n<DataTable className=\"rounded-xl shadow-sm\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nTo remove or strengthen the default frame, override it directly:\n\n```tsx\n<DataTable className=\"border-0\" model={model}>\n  {/* columns */}\n</DataTable>\n\n<DataTable className=\"border-2\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nFor pixel sizing, prefer `style` (`style={{ height: 360 }}`) — the table needs a measurable height to virtualize, and Tailwind's `h-*` utilities work too, as long as the resolved height is real (not a percentage of an unmeasured parent).\n\n## What this page doesn't cover\n\nTwo adjacent concerns have their own pages:\n\n- **Replacing the table's structural components** (Row, GroupHeader, sticky wrappers, scroll element) lives in [Replacing Internals](/data-table/customization/replacing-internals/). That's the `components` prop pattern.\n- **Default classes baked into the shadcn wrapper** — change those by editing `@/components/ui/data-table` directly. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/) for the wrapper anatomy.\n","frontmatter":{"title":"Styling Cells, Headers, and Rows","description":"Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.","sidebar":{"label":"Styling"}},"isInternal":false,"tokens":1218,"sizeBytes":5629},{"name":"02.replacing-internals.md","path":"packages/data-table/docs/7.customization/02.replacing-internals.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/02.replacing-internals.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Replacing Internals\ndescription: Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.\nsidebar:\n  label: Replacing Internals\n---\n\nMost styling you do to a table is column-level — a class on a cell, a renderer that builds a status pill. But some changes apply across every row at once: hover states, density, dividers, the markup around sticky columns, the element that actually scrolls. Those changes live in the table's structural slots, and you change them by passing a replacement component.\n\nThe `components` prop is the channel. Each key in `components` swaps out one piece of the table's internal markup with your own component.\n\n```tsx\nimport * as React from 'react'\nimport type { RowComponentProps } from '@virtuoso.dev/data-table'\n\nconst components = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b transition-colors hover:bg-muted/50\" {...props} />\n  }),\n}\n\n;<DataTable components={components} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nThe override receives the same props the default `<div>` would have received — class, style, children, data attributes, the table's `context` value. Forward them all, or the table loses track of the element. The forwarded `ref` is what the virtualizer reads sizes from, so passing it to the root element is mandatory; the table will appear broken without it.\n\n## What you can replace\n\nThe `components` prop covers structural slots that appear repeatedly or wrap whole regions:\n\n- `Row` — the element used for data rows (one per visible row). Hover states, dividers, alternating backgrounds, density, row-level transitions.\n- `StickyHeader` — the wrapper around the sticky header row. Backgrounds and borders that need to match your page chrome instead of the default.\n- `StickyColumnContainer` — the wrapper around each sticky column region, used in both header and body. The `data-sticky` attribute on the props tells you whether it's the left or right side.\n- `LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter` — the three loading-state slots. Covered separately in [Empty and Loading States](/data-table/customization/empty-and-loading-states/) because each one has its own narrative.\n\n```tsx\nimport type { DataTableComponents, RowComponentProps, StickyColumnContainerComponentProps } from '@virtuoso.dev/data-table'\n\nconst components: DataTableComponents = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b\" {...props} />\n  }),\n  StickyColumnContainer: React.forwardRef<HTMLDivElement, StickyColumnContainerComponentProps>(function Sticky(props, ref) {\n    const side = props['data-sticky']\n    return <div ref={ref} className={side === 'left' ? 'bg-card shadow-[1px_0_0_0_hsl(var(--border))]' : 'bg-card'} {...props} />\n  }),\n}\n```\n\n## Two slots that aren't in `components`\n\nA few replacements have their own top-level props instead of living under `components`:\n\n- `EmptyPlaceholder` — rendered when the model has zero rows. Top-level prop because most apps customize it and want a single explicit place to look.\n- `ScrollElement` — the element that actually scrolls. Top-level prop because it interacts with viewport ownership (`useWindowScroll`, `customScrollParent`) — see [Scroll Containers](/data-table/customization/scroll-containers/).\n\nBoth follow the same forward-the-props rule.\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n;<DataTable EmptyPlaceholder={EmptyPlaceholder} ScrollElement={ScrollElement} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## When to edit the wrapper instead\n\nThe shadcn wrapper at `@/components/ui/data-table` already supplies defaults for every slot. Two questions decide where your override belongs:\n\n- **Does every table in your app want this change?** Edit the default in the wrapper file. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/).\n- **Does this one table want something different?** Pass `components` (or `EmptyPlaceholder` / `ScrollElement`) on that table only.\n\nBoth routes hit the same slots; the difference is scope.\n\n## Don't reach for `data-testid` as a styling hook\n\nPer the data-table conventions, `data-testid` is reserved for tests. When your override needs a styling hook, use a semantic data attribute (`data-density=\"compact\"`) or a className — not a test ID. The table itself never styles against `data-testid` and you shouldn't either.\n","frontmatter":{"title":"Replacing Internals","description":"Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.","sidebar":{"label":"Replacing Internals"}},"isInternal":false,"tokens":1043,"sizeBytes":4738},{"name":"03.empty-and-loading-states.md","path":"packages/data-table/docs/7.customization/03.empty-and-loading-states.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/03.empty-and-loading-states.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Empty, Loading, and Error States\ndescription: Customize empty placeholders and remote loading states without changing data model.\nsidebar:\n  label: Empty and Loading States\n---\n\nThe table has three \"no rows\" states: it has zero rows to display (empty), it's loading rows that haven't arrived yet (loading), or a load failed (error). Each state has its own slot component, and the shadcn wrapper ships sensible defaults for all of them — override a slot when the default copy doesn't fit your domain.\n\n## Empty placeholder\n\n`EmptyPlaceholder` renders when the model has zero rows. The component receives the table `context` prop.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel<{ name: string }>({ data: [] })\n\nfunction EmptyState({ context }: { context: { query: string } }) {\n  return <div className=\"p-6 text-sm text-muted-foreground\">No products match \"{context.query}\".</div>\n}\n\nexport default function App() {\n  return (\n    <DataTable EmptyPlaceholder={EmptyState} className=\"rounded-xl\" context={{ query: 'archived' }} model={model} style={{ height: 240 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Remote loading and error state\n\n`remoteModel()` drives three slot components:\n\n- `LoadingPlaceholder` — `initial` phase (first unresolved dataset)\n- `LoadingOverlay` — `refresh` phase (request params changed, old rows still visible)\n- `LoadingFooter` — `end` phase (append mode is fetching more rows below)\n\nOffset viewport fetches show up as placeholder rows, not a table-level loading slot. `DataTableLoadingState` exposes a `start` segment for custom integrations, but the built-in `remoteModel()` never sets it.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }: LoadingComponentProps) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nThe shadcn wrapper ships defaults for all three slots. Override them for different copy, skeletons, or error recovery controls.\n\nExternal UI reads the same state through `useVirtuosoLoadingState()` (inside the table) or `loadingState$` with remote engine hooks (outside).\n\n## Error recovery\n\nErrors attach to the phase that failed, so a first-load failure can be handled differently from a refresh failure:\n\n```tsx\nfunction LoadingPlaceholder({ loadingState }: LoadingComponentProps) {\n  if (loadingState.initial.status === 'error') {\n    return (\n      <div className=\"p-6 text-sm\" role=\"status\">\n        Could not load products. Check your connection and try again.\n      </div>\n    )\n  }\n\n  return <div className=\"p-6 text-sm text-muted-foreground\">Loading products...</div>\n}\n\nfunction LoadingFooter({ loadingState }: LoadingComponentProps) {\n  if (loadingState.end.status === 'error') {\n    return <div role=\"status\">Could not load more rows.</div>\n  }\n\n  return loadingState.end.status === 'loading' ? <div>Loading more...</div> : null\n}\n\n;<DataTable components={{ LoadingFooter, LoadingPlaceholder }} model={model} />\n```\n\nRetry buttons belong with the slot that's showing the error: initial failure → retry from the placeholder, refresh failure → retry from the overlay, append failure → retry from the footer.\n","frontmatter":{"title":"Empty, Loading, and Error States","description":"Customize empty placeholders and remote loading states without changing data model.","sidebar":{"label":"Empty and Loading States"}},"isInternal":false,"tokens":815,"sizeBytes":3693},{"name":"04.scroll-containers.md","path":"packages/data-table/docs/7.customization/04.scroll-containers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/04.scroll-containers.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Scroll Containers\ndescription: Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.\nsidebar:\n  label: Scroll Containers\n---\n\nThe default table renders its own scrolling element. Three props change that — pick one when the table shares a viewport with surrounding page chrome, or when virtualization needs to be driven by a different scrolling element.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 80 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\n  return (\n    <div ref={setScrollParent} className=\"h-[360px] overflow-auto rounded-xl border\">\n      <div className=\"sticky top-0 z-10 border-b bg-background px-3 py-2 text-sm font-medium\">Inventory workspace</div>\n      <DataTable computeRowKey={({ data }) => data.id} customScrollParent={scrollParent} increaseViewportBy={360} model={model}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Fill available panel height\n\nFor app pages where the table should consume the remaining space below fixed page chrome, keep the default internal table scroller and give the table a measured flex slot. This is usually better than guessing a pixel height.\n\nThe important pieces are:\n\n- a parent with a real height, such as a route shell, panel, or card body\n- `min-h-0` on each flex ancestor between that measured parent and the table\n- `shrink-0` on controls above the table\n- `flex-1 min-h-0` on `DataTable`, plus `style={{ height: '100%' }}`\n\n```tsx\nexport function OrdersPage() {\n  return (\n    <section className=\"flex h-full min-h-0 flex-col\">\n      <PageHeader className=\"shrink-0\" />\n\n      <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n        <Toolbar className=\"shrink-0\" />\n\n        <DataTable className=\"min-h-0 flex-1\" computeRowKey={({ data }) => data.id} model={model} style={{ height: '100%' }}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Order</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </section>\n  )\n}\n```\n\nUse this pattern only when the parent is height-constrained. If the table lives in normal document flow, keep a fixed table height (`style={{ height: 360 }}`) or intentionally switch to `useWindowScroll` / `customScrollParent`.\n\n## Window scrolling\n\n`useWindowScroll` makes the document viewport drive the table's scroll state:\n\n```tsx\n<DataTable model={model} useWindowScroll>\n  {/* columns */}\n</DataTable>\n```\n\nThe table no longer renders an internal scroller; its height comes from rendered content and the surrounding page layout. Sticky headers now interact with document-level sticky offsets, so test them against your actual header/nav stack.\n\n## Existing parent element\n\n`customScrollParent` attaches the table to an existing scrolling element:\n\n```tsx\nconst [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)\n\nreturn (\n  <div ref={setScrollParent} className=\"h-[480px] overflow-auto\">\n    <Toolbar />\n    <DataTable customScrollParent={scrollParent} model={model}>\n      {/* columns */}\n    </DataTable>\n  </div>\n)\n```\n\n## Custom scroll element\n\n`ScrollElement` replaces the default scroller component while keeping the rest of the table contained:\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n<DataTable ScrollElement={ScrollElement} model={model} />\n```\n\nPick one scroll mode per table. For `customScrollParent`, make sure that element has a real height and is the element that actually scrolls.\n","frontmatter":{"title":"Scroll Containers","description":"Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.","sidebar":{"label":"Scroll Containers"}},"isInternal":false,"tokens":1059,"sizeBytes":4654},{"name":"05.ambient-context.md","path":"packages/data-table/docs/7.customization/05.ambient-context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/05.ambient-context.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Ambient Context\ndescription: A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.\nsidebar:\n  label: Ambient Context\n---\n\nThe `context` prop is the table's _ambient context_: a single bag of values that's available throughout the table's customizable parts — `EmptyPlaceholder`, the loading components, `ScrollElement`, `computeRowKey`, structural component overrides. You set it once on `<DataTable>` and the table passes it to every callback and component override that reads it, without per-callback prop wiring.\n\nTypical contents are values the surrounding screen owns but the table's slots need to read:\n\n- the current search query, so `EmptyPlaceholder` can render \"no results for _archived_\"\n- the active tenant ID, so `computeRowKey` can scope keys across saved views\n- a sticky-header offset the parent layout controls, so a custom `ScrollElement` can honor it\n\n```tsx\nfunction EmptyPlaceholder({ context }) {\n  return <div>{context.emptyMessage}</div>\n}\n\n;<DataTable\n  EmptyPlaceholder={EmptyPlaceholder}\n  computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`}\n  context={{ emptyMessage: 'No archived products', tenantId: 'acme' }}\n  model={model}\n>\n  {/* columns */}\n</DataTable>\n```\n\nCompared to wiring each callback to its own props or closing over surrounding state, `context` is the right channel when several internals consume the same value (a search query the empty state needs to display _and_ the loading state references) or when the value comes from above the table component (a router param, an auth scope, a feature flag).\n\n## What receives context\n\nThe same `context` value reaches:\n\n- [`computeRowKey`](/data-table/data-model/row-keys/) — for scoping row keys across tenants, saved views, or table instances that might share row IDs.\n- `EmptyPlaceholder` — for the failed-search query, the filter that returned zero rows, or recovery copy.\n- Loading slots (`LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter`) — for status copy or retry handlers that depend on screen state.\n- Row component overrides — for density flags, alternating-row styling toggles, hover-state preferences.\n- Sticky wrapper overrides — for sticky background colors that match the surrounding chrome.\n- Custom scroll elements — for layout flags, scrollbar styling, or whether to honor a sticky header height.\n\n## What doesn't receive context\n\nCell and header renderers don't receive `context`. They run inside the table's column-virtualization loop, where threading context through every cell on every scroll would be a measurable cost in a wide table. For values cells need to read, close over screen-local state directly when the renderer is inline, or use React context (`useContext`) when the renderer is extracted:\n\n```tsx\nconst FilterContext = React.createContext<string>('')\n\nfunction ProductCell({ row }: CellRenderParams) {\n  const filter = React.useContext(FilterContext)\n  return <span>{highlight(row.data.name, filter)}</span>\n}\n```\n\nThe two channels — React's `useContext` and the table's `context` prop — coexist cleanly: React context for what cell renderers need, the table's `context` prop for what infrastructure needs.\n\n## When to use context vs. closures\n\nFor values defined right above the table in the same component, a closure is simpler:\n\n```tsx\nfunction ProductsTable({ searchQuery, tenantId }: Props) {\n  return (\n    <DataTable\n      EmptyPlaceholder={() => <div>No products match \"{searchQuery}\"</div>}\n      computeRowKey={({ data }) => `${tenantId}:${data.id}`}\n      model={model}\n    >\n      {/* columns */}\n    </DataTable>\n  )\n}\n```\n\n`context` becomes the better choice when the callbacks are extracted (a shared `EmptyPlaceholder` component lives in a design system, not in this file), when one value is consumed by several callbacks at once, or when the table is rendered from a higher-up shell and the callbacks live further down the tree.\n","frontmatter":{"title":"Ambient Context","description":"A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.","sidebar":{"label":"Ambient Context"}},"isInternal":false,"tokens":860,"sizeBytes":4059},{"name":"06.header-slots.md","path":"packages/data-table/docs/7.customization/06.header-slots.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/06.header-slots.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Header Slots\ndescription: Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.\nsidebar:\n  label: Header Slots\n---\n\nA column header has four slot positions for additional UI. Resize handles, reorder grips, sort buttons, filter menus, and selection toggles all mount through one of them.\n\n```text\n┌──────────────── HeaderOverlay ────────────────┐\n│ [HeaderStart]  Header label  [HeaderEnd]  |Edge│\n└───────────────────────────────────────────────┘\n```\n\n- `HeaderStart` — before the label, in normal flex layout. Typical for buttons and menus that sit alongside the label text.\n- `HeaderEnd` — after the label, in normal flex layout. Same use cases as `HeaderStart`, opposite side.\n- `HeaderEdge` — pinned to the column boundary, outside the label's flex flow. Resize handles use this; the position is independent of label length.\n- `HeaderOverlay` — covers the whole header. Drop indicators and custom overlays use this; the slot paints over both the label and any in-flow slots.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge, HeaderEnd } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14 },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const [sortField, setSortField] = React.useState<'product' | 'stock'>('product')\n  const sortedRows = React.useMemo(() => {\n    return [...rows].sort((a, b) => String(a[sortField]).localeCompare(String(b[sortField]), undefined, { numeric: true }))\n  }, [sortField])\n\n  React.useEffect(() => {\n    model.setData(sortedRows)\n  }, [sortedRows])\n\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'product')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd>\n            {({ column }) => (\n              <button className=\"rounded border px-2 py-0.5 text-xs\" onClick={() => setSortField(column.field as 'stock')} type=\"button\">\n                {sortField === column.field ? 'Sorted' : 'Sort'}\n              </button>\n            )}\n          </HeaderEnd>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nSlots stack: a single header can host all four at once. Reorder grip on the left, sort button on the right, resize handle at the edge, drop indicator spanning the whole header for the reorder gesture.\n\n```tsx\nimport { HeaderEdge, HeaderEnd, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\n;<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n    <HeaderEdge component={ResizeHandle} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## The render-function rule\n\nWhen a header contains any slots, the label must be a single render-function child (`{() => 'Product'}`) rather than a plain string. The header needs a function to evaluate alongside the slots; a string child only works for plain headers without slots. See [Cell and Header Renderers](/data-table/columns/cell-and-header-renderers/) for the broader story on render functions.\n\n## What slot components receive\n\nSlot components are passed the column key, the column metadata, the column state, and the header element ref — enough to publish engine actions, render indicators, and position overlays without DOM queries. That ref is what lets a drop-indicator overlay measure the header it's painting over, and what lets an edge-pinned handle compute its drag delta.\n\n## Where this shows up in the shadcn registry\n\nThe opt-in column features ship slot components that drop straight into these positions:\n\n- `ResizeHandle` from `@/components/ui/data-table/column-resize` — mounts in `HeaderEdge`. Install with registry item `data-table-resize-handle`. See [Column Resizing](/data-table/columns/column-resizing/).\n- `ReorderGrip` and `ReorderDropZone` from `@/components/ui/data-table/column-reorder` — mount in `HeaderStart` and `HeaderOverlay` respectively. See [Column Reordering](/data-table/columns/column-reordering/).\n- `SortHeaderButton` from `@/components/ui/data-table/column-sort` — mounts in `HeaderEnd` and dispatches a model `sort` action. Install with registry item `data-table-sort-header-button`. See [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\nCustom sort, filter, menu, and selection controls follow the same pattern: a component that takes the slot's render params and returns whatever JSX you need.\n","frontmatter":{"title":"Header Slots","description":"Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.","sidebar":{"label":"Header Slots"}},"isInternal":false,"tokens":1389,"sizeBytes":6260},{"name":"07.shadcn-wrapper.md","path":"packages/data-table/docs/7.customization/07.shadcn-wrapper.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/07.shadcn-wrapper.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Inside the shadcn Wrapper\ndescription: Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.\nsidebar:\n  label: The shadcn Wrapper\n---\n\nThis page applies to the shadcn install path. If you installed `@virtuoso.dev/data-table` directly without the registry, you own the equivalent components in your own files — the wrapper-vs-headless split still applies, but the file locations are yours to decide. See [Headless Install](/data-table/installation/headless/) for that route.\n\nRunning `npx shadcn add` copied a folder of components into `@/components/ui/data-table`. That folder is your code — it ships in your repo, it's checked into your git history, and editing it doesn't fork the table's behavior. The behavior lives somewhere else.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { product: 'Standing Desk', owner: 'Avery', status: 'Launch ready' },\n  { product: 'USB-C Dock', owner: 'Morgan', status: 'In review' },\n  { product: 'Mechanical Keyboard', owner: 'Riley', status: 'Backorder' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"owner\">\n        <DataTableColumnHeader>Owner</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-muted px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What the wrapper file owns\n\nThe wrapper covers _presentation_:\n\n- default row, header, sticky-column, empty, and loading components\n- Tailwind classes for cells, headers, and group headers\n- the default table frame (`rounded-md border`) and table surface variables\n- ergonomic names (`DataTable`, `DataTableColumn`, `DataTableColumnHeader`, `DataTableCell`)\n- re-exports of the headless hooks and helpers you'll use day-to-day\n\nEdit it like any other component in your app. Tailwind classes, default props, alternate variants, additional re-exports — all fair game. The wrapper file imports behavior from `@virtuoso.dev/data-table`, so your styling edits never fork table mechanics.\n\n## Table surface variables\n\nThe default shadcn wrapper gives `DataTable` an app-level frame (`rounded-md border`) and defines a small set of CSS variables on the same root. The frame border uses `--data-table-border`; callers do not need to add `rounded-md border` at each table instance. Use `className` only for intentional overrides such as `rounded-xl`, `border-0`, `border-2`, `shadow-sm`, or table variable overrides.\n\nThe variables matter most for sticky headers and sticky columns: those pieces are separate overlay containers, so they need an opaque background, but that background should match the table body instead of being hardcoded independently.\n\n| Variable                    | Default                        | Used for                                           |\n| --------------------------- | ------------------------------ | -------------------------------------------------- |\n| `--data-table-bg`           | `hsl(var(--background))`       | Table body, sticky header, sticky column wrappers  |\n| `--data-table-fg`           | `hsl(var(--foreground))`       | Default table/header/group-header text             |\n| `--data-table-border`       | `hsl(var(--border))`           | Row separators, header borders, loading containers |\n| `--data-table-muted`        | `hsl(var(--muted))`            | Group rows, skeleton rows, sticky hover blends     |\n| `--data-table-muted-fg`     | `hsl(var(--muted-foreground))` | Loading and secondary wrapper text                 |\n| `--data-table-row-hover`    | `hsl(var(--muted) / 0.5)`      | Non-sticky row hover background                    |\n| `--data-table-sticky-hover` | a mix of muted and table bg    | Sticky column hover background                     |\n\nIf your app maps shadcn tokens to a different design system, override these variables once on the `DataTable` root or in the copied wrapper defaults. Do not style sticky cells individually just to fix a surface mismatch.\n\n```tsx\n<DataTable\n  className=\"\n    [--data-table-bg:hsl(var(--surface-container-lowest))]\n    [--data-table-fg:hsl(var(--on-surface))]\n    [--data-table-border:hsl(var(--outline))]\n    [--data-table-muted:hsl(var(--surface-container-low))]\n    [--data-table-muted-fg:hsl(var(--on-surface-variant))]\n  \"\n  model={model}\n/>\n```\n\n## What the headless package owns\n\n`@virtuoso.dev/data-table` covers _behavior_:\n\n- virtualization, measurement, scrolling\n- `localModel()` and `remoteModel()`\n- the reactive engine (`engineRef`, `useRemoteCellValue`, `useRemotePublisher`)\n- opt-in feature modules (resize, reorder, visibility, persistence)\n\nBehavior imports always come from `@virtuoso.dev/data-table`. If you find yourself reaching into the headless package to change behavior, that's a signal you want an issue or a PR rather than a fork.\n\n## Feature-specific wrappers\n\nSome opt-in features ship their own wrapper components, following the same presentation/behavior split:\n\n- `data-table-resize-handle` — resize handle UI\n- `data-table-sort-header-button` — model-backed sort button UI\n- `data-table-reorder-grip`, `data-table-reorder-drop-zone`, `data-table-draggable-group-header` — reorder UI\n\nThese names are registry item names, not import paths; shadcn installs them under nested paths such as `@/components/ui/data-table/column-resize` and `@/components/ui/data-table/column-sort`. Each one is header-slot UI you can restyle. The streams they publish to live in the corresponding `@virtuoso.dev/data-table/...` subpaths — see [Header Slots](/data-table/customization/header-slots/) for how those slots compose, and the column feature pages for what each registry component does.\n\n## When you'll come back here\n\nThe wrapper is the right place to change:\n\n- default Tailwind classes on `DataTable`, `DataTableCell`, `DataTableColumnHeader`\n- the default table frame, radius, border, and surface variables\n- the default `Row`, `EmptyPlaceholder`, `LoadingPlaceholder` your team should see\n- ergonomic re-exports you add for your codebase\n\nFor replacing the table's structural components (Row, GroupHeader, sticky wrappers) on a per-instance basis without touching the wrapper file, see [Replacing Internals](/data-table/customization/replacing-internals/).\n","frontmatter":{"title":"Inside the shadcn Wrapper","description":"Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.","sidebar":{"label":"The shadcn Wrapper"}},"isInternal":false,"tokens":1570,"sizeBytes":7058},{"name":"08.card-mode.md","path":"packages/data-table/docs/7.customization/08.card-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/7.customization/08.card-mode.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Card Mode\ndescription: Switch the same data model between table rows and an automatically measured grid of equal-sized cards.\nsidebar:\n  label: Card Mode\n---\n\nUsers can switch between a table and cards without losing their sorting or filtering. Your application owns the switch; the table keeps the same data model connected in either presentation.\n\n## Switch the presentation\n\nPass `mode=\"card\"` and a `components.Card` component to show cards. The default mode is `\"table\"`. Each switch starts at the first item; it does not restore the previous scroll position or repeat the model handshake.\n\n`Card` receives `{ data, index, context }` inside a wrapper that the library measures. The optional `CardHeader` receives `{ context }` and stays above the cards as a sticky header. Both are inside the same engine provider as the table, so header controls can read model action state and dispatch sorting or filtering actions.\n\nThis example uses the editable [shadcn wrapper](../shadcn-wrapper/) and keeps its column declarations mounted in both modes.\n\n```tsx live wide\nimport * as React from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { dispatchModelAction$, localModel, modelActionState$, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport type { CardComponentProps } from '@virtuoso.dev/data-table'\n\ninterface Service {\n  id: number\n  name: string\n  requests: number\n}\n\nconst services: Service[] = Array.from({ length: 1000 }, (_, id) => ({\n  id,\n  name: `Service ${id}`,\n  requests: (id * 137) % 10000,\n}))\n\nfunction ServiceCard({ data }: CardComponentProps<Service>) {\n  return (\n    <article>\n      <h3 className=\"font-medium\">{data.name}</h3>\n      <p>{data.requests.toLocaleString()} requests</p>\n    </article>\n  )\n}\n\nfunction Controls() {\n  const dispatch = usePublisher(dispatchModelAction$)\n  const state = useCellValue(modelActionState$)\n  return (\n    <label className=\"flex items-center gap-2 bg-background p-3\">\n      <input\n        type=\"checkbox\"\n        checked={Boolean(state.sort?.payload)}\n        onChange={(event) => dispatch({ action: 'sort', payload: event.target.checked })}\n      />\n      Descending requests\n    </label>\n  )\n}\n\nconst components = { Card: ServiceCard, CardHeader: Controls }\n\nexport default function App() {\n  const [mode, setMode] = React.useState<'card' | 'table'>('card')\n  const model = React.useMemo(\n    () =>\n      localModel<Service>({\n        data: services,\n        pipeline: ['sort'],\n        actions: {\n          sort: {\n            stage: 'sort',\n            handler: ({ data, payload }: { data: Service[]; payload: unknown }) =>\n              payload ? data.toSorted((a, b) => b.requests - a.requests) : data,\n          },\n        },\n      }),\n    []\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <button className=\"rounded border px-3 py-1\" onClick={() => setMode(mode === 'card' ? 'table' : 'card')}>\n        Show {mode === 'card' ? 'table' : 'cards'}\n      </button>\n      <DataTable<Service, unknown, never>\n        model={model}\n        mode={mode}\n        components={components}\n        computeRowKey={({ data }) => data.id}\n        cardListClassName=\"grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-3\"\n        cardItemClassName=\"h-36 min-w-0 overflow-hidden rounded border p-4\"\n        style={{ height: 380 }}\n      >\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Service</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"requests\">\n          <DataTableColumnHeader>\n            <Controls />\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nFor filtering or remote sorting, connect controls to the same [model actions](../../data-model/) you use in table mode. Card mode does not generate operation controls or choose breakpoints.\n\n## Define a uniform CSS layout\n\nStyle the list with `cardListClassName` and its item wrappers with `cardItemClassName`. The renderer measures one representative wrapper, the container width, and the computed CSS gaps. It derives the number of columns and virtualizes whole lines. There is no numeric card-size prop.\n\n- Every card must have the same outer width and height at the current container width. Matching heights within each individual grid row is not sufficient.\n- Use a regular row-major CSS grid, such as `repeat(auto-fill, minmax(200px, 1fr))`, or a wrapping flex layout with equal-width items and `flex-shrink: 0`.\n- Use `gap`, `row-gap`, and `column-gap` with lengths that resolve to pixels for spacing. Percentage gaps, item margins, spanning items, dense placement, and space-distributed alignment are not supported.\n- Keep list padding and borders off the measured list. The library owns its vertical padding for virtualization. Put decoration on the card wrappers or outside the table.\n- CSS may change dimensions and gaps responsively. A resize observer remeasures the active layout, including after a hidden container becomes visible.\n- Remote placeholders must use the same dimensions as loaded cards. Supply a schema-compatible placeholder and handle it in `Card`.\n\nCard dimensions never enter the table row measurement cache, and row heights never seed card geometry. `itemHeight$` remains a **table-row** measurement API. Each presentation remeasures when activated; retained data and column settings do not imply retained geometry.\n\nUse the local Card Mode Ladle preview to change container width and card height independently, then switch to table rows of a different height.\n\n## Scrolling and loading\n\n`initialLocation`, `scrollToRow$`, and `scrollIntoView$` refer to **model item indexes**, not visual grid-row indexes. The same is true of `viewportRange$` and remote-model viewport requests. `increaseViewportBy` adds pixel overscan, and `onRenderedDataChange` reports the mounted model items.\n\nCards support internal scrolling, `useWindowScroll`, and `customScrollParent`. Their sticky header is included in the visible-height calculation. Empty, initial-loading, refresh-overlay, and append-footer slots use the same model loading state as the table. Switching during a request does not cancel or restart it; the new viewport can request additional data normally.\n\nSee [Scroll Containers](../scroll-containers/) for scroll-owner setup and [Empty and Loading States](../empty-and-loading-states/) for loading and retry controls. The Card Mode Ladle previews include remote offset and append models with delayed requests and simulated errors.\n\n## Limits and recovery\n\nCard mode requires `components.Card` and ungrouped model data. Otherwise it displays a configuration error. Providing the missing component, sending an ungrouped result, or returning to table mode recovers without replacing the model. It does not silently flatten groups or change your grouping action.\n\nColumn headers, column resizing, sticky columns, and column reordering are table-only surfaces. Their declarations and configuration remain available when the application switches back.\n\nUse table mode when users need grouped rows. For unequal card dimensions or masonry placement, use a dedicated layout such as [Virtuoso Masonry](../../../masonry/) instead of this uniform card presentation.\n","frontmatter":{"title":"Card Mode","description":"Switch the same data model between table rows and an automatically measured grid of equal-sized cards.","sidebar":{"label":"Card Mode"}},"isInternal":false,"tokens":1607,"sizeBytes":7492},{"name":"01.basic-table.md","path":"packages/data-table/docs/8.examples/01.basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/01.basic-table.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Basic Table Example\ndescription: Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.\nsidebar:\n  label: Basic Table\n---\n\nThree columns, a hardcoded product list, and the styled shadcn wrapper. The shape you'll arrive at on day one when you drop the table into a screen, before any backend wiring, sorting, or pinning enters the picture.\n\nThe purpose of this example is to fix that day-one shape. The `data` array is passed to `localModel`; each `<DataTableColumn>` declares a `field` and the cell renderer for that field. Nothing is lifted into React state — the table owns its own scroll, selection, and layout state through the engine, and the column declarations live alongside the markup that uses them. Every later example in this section adds one capability on top of this skeleton; if anything below ever feels too dense, this is the page to return to.\n\n## APIs used\n\n- `DataTable` for scrolling and virtualization\n- `DataTableColumn` for declarative columns\n- `DataTableColumnHeader` and `DataTableCell` for presentation\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## What this leaves out\n\n- Grouped rows — add `groups` markers and a `GroupHeaderCell`\n- Sticky pinning — add `sticky=\"left\"` or `sticky=\"right\"`\n- Remote data — swap `localModel` for `remoteModel`\n- Stable row identity — add `computeRowKey` once rows can sort, filter, or move\n","frontmatter":{"title":"Basic Table Example","description":"Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.","sidebar":{"label":"Basic Table"}},"isInternal":false,"tokens":699,"sizeBytes":2983},{"name":"03.grouped-data.md","path":"packages/data-table/docs/8.examples/03.grouped-data.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/03.grouped-data.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Group Rows by Region and Category\ndescription: Organize a large inventory table into nested groups while preserving virtualization and sticky headers.\nsidebar:\n  label: Grouped Rows\n---\n\nThis example precomputes a flattened grouped result, then lets the table virtualize group headers and product rows together. Regions are top-level group rows, categories are second-level group rows, and products remain ordinary data rows.\n\n## APIs used\n\n- `localModel({ data, groups })` receives the flattened row stream and group markers.\n- `groups: [{ index, level }]` identifies which rows are group headers and how deeply they nest.\n- `GroupHeaderCell` renders the marked group rows instead of rendering column cells for them.\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { groupedProducts } from './data'\nimport type { GroupLabel, Product } from './data'\n\nconst model = localModel(groupedProducts)\n\nfunction rowKey(row: GroupLabel | Product) {\n  return 'id' in row ? row.id : row.groupKey\n}\n\nfunction productName(row: GroupLabel | Product) {\n  return 'name' in row ? row.name : ''\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => rowKey(data)} model={model} style={{ height: 420 }}>\n      <GroupHeaderCell>\n        {({ level, row }) => (\n          <div className={level === 0 ? 'bg-muted px-3 py-2 font-semibold' : 'bg-muted/50 px-6 py-2 font-medium'}>\n            {(row.data as { label: string }).label}\n          </div>\n        )}\n      </GroupHeaderCell>\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => productName(row.data)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"region\">\n        <DataTableColumnHeader>Region</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  region: 'US' | 'EU'\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface GroupLabel {\n  groupKey: string\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\nconst regions: Product['region'][] = ['US', 'EU']\n\nconst products: Product[] = Array.from({ length: 90 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    region: regions[index % regions.length]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport const groupedProducts: {\n  data: (GroupLabel | Product)[]\n  groups: { index: number; level: number }[]\n} = (() => {\n  const data: (GroupLabel | Product)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const region of regions) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ groupKey: `region-${region}`, label: `Region: ${region}` })\n\n    for (const category of categories) {\n      const rows = products.filter((product) => product.region === region && product.category === category)\n      groups.push({ index: data.length, level: 1 })\n      data.push({ groupKey: `region-${region}-category-${category}`, label: `${category} (${rows.length})` })\n      data.push(...rows)\n    }\n  }\n\n  return { data, groups }\n})()\n```\n\n## Notes\n\nGroup labels are virtualized rows, so deep nesting still scrolls cheaply. The practical limit is readability: two levels is usually the ceiling before labels need to be very short. If the grouping changes at runtime, keep stable keys on both product rows and group rows so React can preserve row identity as sections move.\n","frontmatter":{"title":"Group Rows by Region and Category","description":"Organize a large inventory table into nested groups while preserving virtualization and sticky headers.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":1028,"sizeBytes":4285},{"name":"04.sticky-columns.md","path":"packages/data-table/docs/8.examples/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/04.sticky-columns.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Sticky Columns Example\ndescription: Pin identity and status columns so users can scan wide datasets without losing the row context.\nsidebar:\n  label: Sticky Columns\n---\n\nA product column pinned to the left, a status column pinned to the right, and a dozen metric columns in between to force horizontal scrolling — the only situation where sticky columns matter. The toolbar above the table flips the status column's sticky side at runtime through the engine.\n\n## APIs used\n\n- `sticky=\"left\"` / `sticky=\"right\"` — declarative pin\n- `setColumnSticky$` — runtime sticky changes from external UI\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, setColumnSticky$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport { metricFields, products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'right' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status right\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\" sticky=\"left\">\n          <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n\n        {metricFields.map((field) => (\n          <DataTableColumn field={field} key={field}>\n            <DataTableColumnHeader className=\"min-w-[140px]\">{field}</DataTableColumnHeader>\n            <DataTableCell className=\"tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        ))}\n\n        <DataTableColumn field=\"status\" sticky=\"right\">\n          <DataTableColumnHeader className=\"min-w-[120px]\">Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const metricFields = Array.from({ length: 12 }, (_, index) => `metric${index + 1}`)\n\nexport const products = Array.from({ length: 120 }, (_, rowIndex) => {\n  const stock = (rowIndex * 5) % 32\n  const base: Record<string, string | number> = {\n    id: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n    name: `Product ${rowIndex + 1}`,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n\n  for (const field of metricFields) {\n    base[field] = (rowIndex + 1) * 7 + field.charCodeAt(field.length - 1)\n  }\n\n  return base\n})\n```\n\n## Notes\n\nEach pinned column takes width from the scrollable area. Past two or three sticky columns, horizontal scrolling stops being useful — you've spent your row width on pinned content and the remaining viewport can no longer fit anything meaningful.\n\nThe runtime toggle uses `setColumnSticky$` against the same column key the table assigned during declaration. Look up the key by field with `columns$` once at the call site instead of holding it in component state; the engine is the source of truth.\n","frontmatter":{"title":"Sticky Columns Example","description":"Pin identity and status columns so users can scan wide datasets without losing the row context.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1048,"sizeBytes":4554},{"name":"06.column-reorder.md","path":"packages/data-table/docs/8.examples/06.column-reorder.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/06.column-reorder.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Reorder Example\ndescription: Reorder columns with the shadcn drag handle and drop zone components.\nsidebar:\n  label: Column Reorder\n---\n\nEach header carries a `GripVertical` icon at the start and a transparent drop zone across the rest. Drag a grip over another header to move the source column before or after the target.\n\nThe shadcn components import from `@virtuoso.dev/data-table/column-reorder` — consumers that never reorder columns don't ship that reducer. The grip is a Lucide icon, sized and spaced like the other shadcn header affordances so it visually separates from the column title.\n\n## APIs used\n\n- `ReorderGrip` — draggable header affordance\n- `ReorderDropZone` — header drop target\n- `HeaderStart` / `HeaderOverlay` — slot positions for the components\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 400 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nDrag-and-drop is the spreadsheet-style affordance. For accessible alternatives or deterministic test setup, publish `reorderColumns$` from an external menu or button — the streams accept the same operations the drag UI emits.\n","frontmatter":{"title":"Column Reorder Example","description":"Reorder columns with the shadcn drag handle and drop zone components.","sidebar":{"label":"Column Reorder"}},"isInternal":false,"tokens":649,"sizeBytes":2645},{"name":"07.local-filter-sort-group.md","path":"packages/data-table/docs/8.examples/07.local-filter-sort-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/07.local-filter-sort-group.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Local Filter, Sort, Group\ndescription: Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.\nsidebar:\n  label: Local Filter, Sort, Group\n---\n\nA filter dropdown, a sort dropdown, and a grouping dropdown share one `localModel` pipeline. The pipeline runs filter → sort → group; each control publishes one action and only the affected stage (and the stages after it) rerun.\n\n## APIs used\n\n- `localModel()`\n- `pipeline` to declare transformation order\n- `stage` to attach an action to a pipeline step\n- `model.send()` to publish filter, sort, and group changes\n- `GroupHeaderCell`\n\n```tsx live wide file=App.tsx\nimport { useEffect, useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nimport { FilterBar } from './FilterBar'\nimport { groupProducts, products } from './data'\n\nimport type { Product, ProductGroup } from './data'\n\nfunction isProduct(item: Product | ProductGroup): item is Product {\n  return 'id' in item\n}\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n  const [sortBy, setSortBy] = useState<'name' | 'price' | 'revenue'>('name')\n  const [groupBy, setGroupBy] = useState<'none' | 'category' | 'status'>('category')\n\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['filter', 'sort', 'group'],\n      actions: {\n        filter: {\n          stage: 'filter',\n          handler: ({ data, payload }) => {\n            const nextCategory = payload as string\n            const rows = data.filter(isProduct)\n            return nextCategory === 'all' ? rows : rows.filter((product) => product.category === nextCategory)\n          },\n        },\n        sort: {\n          stage: 'sort',\n          handler: ({ data, payload }) => {\n            const field = payload as 'name' | 'price' | 'revenue'\n            return data.filter(isProduct).toSorted((a, b) => (a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0))\n          },\n        },\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as 'none' | 'category' | 'status'),\n        },\n      },\n    })\n  )\n\n  useEffect(() => {\n    model.send({ action: 'filter', payload: category })\n  }, [category, model])\n\n  useEffect(() => {\n    model.send({ action: 'sort', payload: sortBy })\n  }, [sortBy, model])\n\n  useEffect(() => {\n    model.send({ action: 'group', payload: groupBy })\n  }, [groupBy, model])\n\n  return (\n    <div className=\"space-y-4\">\n      <FilterBar\n        category={category}\n        groupBy={groupBy}\n        onCategoryChange={setCategory}\n        onGroupByChange={setGroupBy}\n        onSortByChange={setSortBy}\n        sortBy={sortBy}\n      />\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <GroupHeaderCell className=\"bg-muted px-3 py-2 font-medium\">{({ row }) => row.data.label}</GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=FilterBar.tsx\ninterface FilterBarProps {\n  category: string\n  groupBy: 'none' | 'category' | 'status'\n  onCategoryChange: (value: string) => void\n  onGroupByChange: (value: 'none' | 'category' | 'status') => void\n  onSortByChange: (value: 'name' | 'price' | 'revenue') => void\n  sortBy: 'name' | 'price' | 'revenue'\n}\n\nexport function FilterBar({ category, groupBy, onCategoryChange, onGroupByChange, onSortByChange, sortBy }: FilterBarProps) {\n  return (\n    <div className=\"flex flex-wrap gap-3\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onCategoryChange(event.target.value)}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onSortByChange(event.target.value as 'name' | 'price' | 'revenue')}\n        value={sortBy}\n      >\n        <option value=\"name\">Sort by name</option>\n        <option value=\"price\">Sort by price</option>\n        <option value=\"revenue\">Sort by revenue</option>\n      </select>\n\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => onGroupByChange(event.target.value as 'none' | 'category' | 'status')}\n        value={groupBy}\n      >\n        <option value=\"none\">No groups</option>\n        <option value=\"category\">Group by category</option>\n        <option value=\"status\">Group by status</option>\n      </select>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n  revenue: number\n  status: 'In stock' | 'Low stock' | 'Backorder'\n}\n\nexport interface ProductGroup {\n  label: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nexport const products: Product[] = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `${categories[index % categories.length]} Item ${index + 1}`,\n    category: categories[index % categories.length]!,\n    price: 49 + (index % 11) * 13,\n    revenue: 1_100 + index * 85,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n\nexport function groupProducts(data: Product[], groupBy: 'none' | 'category' | 'status') {\n  if (groupBy === 'none') {\n    return data\n  }\n\n  const groups = new Map<string, Product[]>()\n\n  for (const item of data) {\n    const key = groupBy === 'category' ? item.category : item.status\n    const bucket = groups.get(key)\n    if (bucket) {\n      bucket.push(item)\n    } else {\n      groups.set(key, [item])\n    }\n  }\n\n  const flatData: (Product | ProductGroup)[] = []\n  const markers: { index: number; level: number }[] = []\n\n  for (const [key, items] of groups) {\n    markers.push({ index: flatData.length, level: 0 })\n    flatData.push({ label: `${key} (${items.length})` })\n    flatData.push(...items)\n  }\n\n  return { data: flatData, groups: markers }\n}\n```\n\n## When to outgrow this shape\n\n- The full dataset doesn't fit in memory — switch to `remoteModel` and move filter/sort/group into request params.\n- Filtering rules need server-side evaluation (permissions, complex SQL, full-text search) — keep the table local for display but fetch filtered rows.\n","frontmatter":{"title":"Local Filter, Sort, Group","description":"Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.","sidebar":{"label":"Local Filter, Sort, Group"}},"isInternal":false,"tokens":1862,"sizeBytes":7409},{"name":"08.remote-offset.md","path":"packages/data-table/docs/8.examples/08.remote-offset.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/08.remote-offset.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Server-side Paging\ndescription: Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.\nsidebar:\n  label: Paging\n---\n\nOffset mode requests only the pages near the viewport. Placeholders fill the rest of the scroll height while pages are pending, so total scroll size stays correct from the first render.\n\nThe fetch function receives `{ offset, limit, params, signal }` and returns `{ rows, totalCount }`.\n\n## APIs used\n\n- `remoteModel()`\n- `defaultOffsetViewportHandler` — built-in handler for offset/limit backends\n- `AbortSignal`-aware fetch functions\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nexport default function App() {\n  const [category, setCategory] = useState('all')\n\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          handler: ({ params, payload }) => ({ ...params, category: payload as string }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <select\n        className=\"h-9 rounded-md border bg-background px-3 text-sm\"\n        onChange={(event) => {\n          const nextCategory = event.target.value\n          setCategory(nextCategory)\n          model.send({ action: 'filter', payload: nextCategory })\n        }}\n        value={category}\n      >\n        <option value=\"all\">All categories</option>\n        <option value=\"Office\">Office</option>\n        <option value=\"Peripherals\">Peripherals</option>\n        <option value=\"Audio\">Audio</option>\n      </select>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(220, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nOffset mode needs the backend to answer count-aware slice queries efficiently. For cursor APIs, search-after pagination, or feeds that don't expose a total count, see the [Cursor Load More example](/data-table/examples/remote-append/).\n","frontmatter":{"title":"Server-side Paging","description":"Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.","sidebar":{"label":"Paging"}},"isInternal":false,"tokens":1030,"sizeBytes":4524},{"name":"09.remote-append.md","path":"packages/data-table/docs/8.examples/09.remote-append.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/09.remote-append.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Endless Scrolling\ndescription: Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.\nsidebar:\n  label: Endless Scrolling\n---\n\nEndless scrolling — append mode — accumulates rows as the user reaches the end of the loaded list. New pages are concatenated onto what's already loaded; there is no total count and no jump-to-row. The fetch function receives `{ cursor, limit, params, signal }` and returns `{ rows, cursor, hasMore }`.\n\n## APIs used\n\n- `mode: 'append'` — turns on append-mode bookkeeping\n- `defaultAppendViewportHandler` — built-in viewport handler that triggers `loadMore` near the end\n- `model.send({ action: 'loadMore' })` — manual load-more trigger\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { defaultAppendViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\n\nimport { fetchProducts } from './api'\n\nimport type { Product } from './api'\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product>({\n      fetch: fetchProducts,\n      initialParams: {},\n      mode: 'append',\n      onViewportChange: defaultAppendViewportHandler,\n      pageSize: 20,\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <Button onClick={() => model.send({ action: 'loadMore' })} variant=\"outline\">\n        Load more\n      </Button>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { AppendFetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 180 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n}))\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: AppendFetchParams<Record<string, never>>) {\n  await pause(220, params.signal)\n  const start = (params.cursor as number | undefined) ?? 0\n  const rows = allProducts.slice(start, start + params.limit)\n  const nextCursor = start + rows.length\n\n  return {\n    cursor: nextCursor,\n    hasMore: nextCursor < allProducts.length,\n    rows,\n  }\n}\n```\n\n## When this doesn't fit\n\nUsers can't jump to row 50,000 without loading everything before it. For known total counts and arbitrary positioning, see the [Server-side Pages example](/data-table/examples/remote-offset/).\n","frontmatter":{"title":"Endless Scrolling","description":"Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.","sidebar":{"label":"Endless Scrolling"}},"isInternal":false,"tokens":835,"sizeBytes":3582},{"name":"10.table-instance-dashboard.md","path":"packages/data-table/docs/8.examples/10.table-instance-dashboard.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/10.table-instance-dashboard.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: External Toolbar\ndescription: Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.\nsidebar:\n  label: External Toolbar\n---\n\nA control panel beside the table reads `scrollLocation$` and publishes `scrollToRow$` and `setColumnSticky$` through a shared `engineRef`. Virtualization and feature state stay inside the table engine; React state stays out of it.\n\n## APIs used\n\n- `useEngineRef()`\n- `useRemoteCellValue()`\n- `useRemotePublisher()`\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, useEngineRef } from '@virtuoso.dev/data-table'\n\nimport { ControlPanel } from './ControlPanel'\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n\n  return (\n    <div className=\"grid gap-4 lg:grid-cols-[280px_minmax(0,1fr)]\">\n      <ControlPanel engineRef={engineRef} />\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 420 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```tsx live file=ControlPanel.tsx\nimport { Button } from '@/components/ui/button'\nimport { columns$, scrollLocation$, scrollToRow$, setColumnSticky$, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nimport type { ColumnInfo, EngineRef } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nexport function ControlPanel({ engineRef }: { engineRef: EngineRef }) {\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n  const setColumnSticky = useRemotePublisher(setColumnSticky$, engineRef)\n\n  const statusKey = findColumnKey(columns, 'status')\n\n  return (\n    <div className=\"space-y-4 rounded-xl border bg-muted/30 p-4 text-sm\">\n      <div className=\"space-y-1\">\n        <p className=\"font-semibold\">Table controls</p>\n        <p className=\"text-muted-foreground\">\n          {scrollLocation === undefined ? 'Waiting for table state…' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n        </p>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 79, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 80\n        </Button>\n      </div>\n\n      <div className=\"flex flex-wrap gap-2\">\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: 'left' })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Pin status left\n        </Button>\n        <Button\n          disabled={!statusKey}\n          onClick={() => {\n            if (statusKey) {\n              setColumnSticky({ key: statusKey, sticky: undefined })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Clear sticky\n        </Button>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nThe cells and actions you publish to (`scrollToRow$`, `setColumnSticky$`, `scrollLocation$`, etc.) are the public API surface for this table — pin their names in tests and treat renames the same way you'd treat a prop rename.\n","frontmatter":{"title":"External Toolbar","description":"Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.","sidebar":{"label":"External Toolbar"}},"isInternal":false,"tokens":1181,"sizeBytes":4946},{"name":"11.scrollbar-overlay.md","path":"packages/data-table/docs/8.examples/11.scrollbar-overlay.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/11.scrollbar-overlay.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Keep Sticky Columns Clear of Overlay Scrollbars\ndescription: Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.\nsidebar:\n  label: Overlay Scrollbars\n---\n\nmacOS overlay scrollbars sit on top of content and can cover the last sticky-right column. The header and cell render params expose `overlaidByScrollbar` — true when an overlay scrollbar covers this cell — so you can add compensating right padding only when needed.\n\n## APIs used\n\n- `overlaidByScrollbar` — render-param flag in cell and header renderers\n- `sticky=\"right\"` — column pin where the bug shows up\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { columns, rows } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 400 }}>\n      <DataTableColumn field=\"sku\" sticky=\"left\">\n        <DataTableColumnHeader>SKU</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      {columns.map((column) => (\n        <DataTableColumn field={column} key={column}>\n          <DataTableColumnHeader className=\"min-w-[130px]\">{column}</DataTableColumnHeader>\n          <DataTableCell>{({ row }) => String(row.data[column])}</DataTableCell>\n        </DataTableColumn>\n      ))}\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[150px] justify-end\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Actions\n            </span>\n          )}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right text-sm text-muted-foreground\">\n          {({ overlaidByScrollbar }) => (\n            <span\n              style={{\n                paddingRight: overlaidByScrollbar ? 'calc(0.75rem + var(--overlay-scrollbar-visible-size))' : '0.75rem',\n              }}\n            >\n              Edit · Duplicate\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const columns = Array.from({ length: 16 }, (_, index) => `metric${index + 1}`)\n\nexport const rows = Array.from({ length: 120 }, (_, rowIndex) => {\n  const row: Record<string, string> = {\n    sku: `SKU-${String(rowIndex + 1).padStart(3, '0')}`,\n  }\n\n  for (const column of columns) {\n    row[column] = `R${rowIndex + 1}-${column}`\n  }\n\n  return row\n})\n```\n\n## Notes\n\nOnly sticky-right columns need this. The bug is platform-specific (macOS, sometimes iPadOS) — easy to miss in development on other platforms, so bake the padding in early.\n\nColumn widths come from header measurements. Set `min-w-*` on the header only; never on `DataTableCell`. A width class on the cell competes with the engine's column-width sync and the cells start overlapping each other when the table re-measures.\n","frontmatter":{"title":"Keep Sticky Columns Clear of Overlay Scrollbars","description":"Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.","sidebar":{"label":"Overlay Scrollbars"}},"isInternal":false,"tokens":741,"sizeBytes":3254},{"name":"12.custom-scroll-container.md","path":"packages/data-table/docs/8.examples/12.custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/12.custom-scroll-container.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Embed a Table in a Shared Scroll Area\ndescription: Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.\nsidebar:\n  label: Shared Scroll Area\n---\n\nThe table renders inside a parent with its own `overflow: auto`, so notes, filters, and the table body all scroll together. `customScrollParent` tells the table to drive virtualization from that outer element instead of its own scroller.\n\n## APIs used\n\n- `customScrollParent` — attaches the table to an external scrolling element\n- An outer container with `overflow: auto` and a real height\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null)\n\n  return (\n    <div className=\"rounded-xl\">\n      <div className=\"border-b px-4 py-3\">\n        <p className=\"font-semibold\">Shared scroll surface</p>\n        <p className=\"text-sm text-muted-foreground\">The note above the table scrolls together with the rows.</p>\n      </div>\n\n      <div className=\"h-[420px] overflow-auto\" ref={setScrollParent}>\n        <div className=\"border-b px-4 py-3 text-sm text-muted-foreground\">\n          Filters, charts, or help text can live in the same scrolling context as the table body.\n        </div>\n\n        <DataTable className=\"min-w-[720px]\" customScrollParent={scrollParent} model={model}>\n          <DataTableColumn field=\"name\">\n            <DataTableColumnHeader>Product</DataTableColumnHeader>\n            <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"category\">\n            <DataTableColumnHeader>Category</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"region\">\n            <DataTableColumnHeader>Region</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n          <DataTableColumn field=\"status\">\n            <DataTableColumnHeader>Status</DataTableColumnHeader>\n            <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n          </DataTableColumn>\n        </DataTable>\n      </div>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 160 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n  }\n})\n```\n\n## Notes\n\nThe outer container's height bounds the entire layout, including the table. Sticky table headers now interact with sibling sticky elements inside the same scroll context — test both together.\n\nDo not set `style={{ height }}` on the table when `customScrollParent` is in use. The table grows to its virtualized content height; the outer container's scroll height drives virtualization. A fixed table height clips the rows before the scroll parent can reach them, and the bottom rows appear to never load.\n","frontmatter":{"title":"Embed a Table in a Shared Scroll Area","description":"Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.","sidebar":{"label":"Shared Scroll Area"}},"isInternal":false,"tokens":812,"sizeBytes":3558},{"name":"13.column-resizing.md","path":"packages/data-table/docs/8.examples/13.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/13.column-resizing.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Resizing Example\ndescription: Add resize handles and external width controls to a table.\nsidebar:\n  label: Column Resizing\n---\n\nA resize handle on every column plus an external button that sets the product column to a fixed width. Both publish to the same `resizeColumn$` stream; `columnWidths$` exposes the realized layout for the readout.\n\n## APIs used\n\n- `HeaderEdge` — slot position for the handle\n- `ResizeHandle` — shadcn drag affordance\n- `resizeColumn$` — width override stream\n- `columnWidths$` — realized layout state for display\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel, columnWidths$, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnWidthOverrides$, resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nimport { products } from './data'\n\nimport type { ColumnInfo } from '@virtuoso.dev/data-table'\n\nfunction findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {\n  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined\n}\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const widths = useRemoteCellValue(columnWidths$, engineRef)\n  const resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n  const resetColumnWidthOverrides = useRemotePublisher(resetColumnWidthOverrides$, engineRef)\n\n  const nameKey = findColumnKey(columns, 'name')\n  const nameWidth = nameKey && widths ? Math.round(widths.get(nameKey) ?? 0) : 0\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n        <Button\n          disabled={!nameKey}\n          onClick={() => {\n            if (nameKey) {\n              resizeColumn({ key: nameKey, width: 280 })\n            }\n          }}\n          variant=\"outline\"\n        >\n          Set product to 280px\n        </Button>\n        <Button onClick={() => resetColumnWidthOverrides()} variant=\"outline\">\n          Reset widths\n        </Button>\n        <span className=\"text-muted-foreground\">Product width: {nameWidth || 'measuring'}px</span>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Stock'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n```\n\n## Notes\n\nDrag handles cover spreadsheet-style interaction. External controls cover presets, reset buttons, and commands that shouldn't depend on a pointer — both publish to the same stream, so they stay consistent.\n","frontmatter":{"title":"Column Resizing Example","description":"Add resize handles and external width controls to a table.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":942,"sizeBytes":4093},{"name":"14.column-visibility.md","path":"packages/data-table/docs/8.examples/14.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/14.column-visibility.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Column Visibility Example\ndescription: Build a toolbar that hides and restores columns at runtime.\nsidebar:\n  label: Column Visibility\n---\n\nA toolbar reads `columns$` and `columnVisibilityState$` through an `engineRef` and publishes `setColumnVisibility$` and `resetColumnVisibility$`. The `internalId` column starts hidden via `visible={false}` and can be toggled back on from the toolbar.\n\n## APIs used\n\n- `columns$` — column declarations from the engine\n- `columnVisibilityState$` — current visibility overrides\n- `setColumnVisibility$` / `resetColumnVisibility$` — toggle and reset\n\n```tsx live wide file=App.tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nimport { products } from './data'\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n          return (\n            <Button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} variant={visible ? 'secondary' : 'outline'}>\n              {visible ? 'Hide' : 'Show'} {column.field}\n            </Button>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 400 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const products = Array.from({ length: 100 }, (_, index) => {\n  const stock = (index * 5) % 32\n\n  return {\n    id: `SKU-${String(index + 1).padStart(3, '0')}`,\n    internalId: `inv_${1000 + index}`,\n    name: `Product ${index + 1}`,\n    category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n    status: stock < 6 ? 'Backorder' : stock < 12 ? 'Low stock' : 'In stock',\n    region: ['US', 'EU', 'APAC'][index % 3]!,\n  }\n})\n```\n\n## Notes\n\nPersisted visibility is keyed by `field`, not by the generated runtime column key. Renaming a field invalidates its saved visibility — see [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the migration rule.\n","frontmatter":{"title":"Column Visibility Example","description":"Build a toolbar that hides and restores columns at runtime.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":937,"sizeBytes":4095},{"name":"15.state-persistence.md","path":"packages/data-table/docs/8.examples/15.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/15.state-persistence.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: State Persistence Example\ndescription: Persist a remote filter action and column widths for a remote table.\nsidebar:\n  label: State Persistence\n---\n\nA remote category filter and the column widths both survive a reload. The model action opts into persistence with `persistence: true`; the column-width adapter saves resize overrides by field name. Both restore on mount through `DataTableStatePersistence`.\n\n## APIs used\n\n- `DataTableStatePersistence` — mounts the adapters under one storage key\n- `modelStatePersistenceAdapter()` — persists model action state\n- `columnWidthPersistenceAdapter()` — persists column width overrides\n- `persistence: true` — opt-in flag on a model action\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst adapters = [modelStatePersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        filter: {\n          persistence: true,\n          handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n        },\n      },\n      fetch: fetchProducts,\n      initialParams: { category: 'all' },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  function applyCategory(nextCategory: string) {\n    model.send({ action: 'filter', payload: nextCategory })\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {['all', 'Office', 'Peripherals', 'Audio'].map((value) => (\n          <Button key={value} onClick={() => applyCategory(value)} variant=\"outline\">\n            {value}\n          </Button>\n        ))}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 420 }}>\n        <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-example\" />\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Product'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Category'}\n          </DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"price\">\n          <DataTableColumnHeader className=\"justify-end\">\n            <HeaderEdge component={ResizeHandle} />\n            {() => 'Price'}\n          </DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  category: string\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.category === 'all' ? allProducts : allProducts.filter((product) => product.category === params.params.category)\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## Notes\n\nRestore happens after columns and the model register with the engine, so a brief unpersisted render is normal on mount. Keep the `adapters` array stable (define outside the component or wrap in `useMemo`), and bump `storageKey` or `resetKey` when saved state should no longer apply.\n","frontmatter":{"title":"State Persistence Example","description":"Persist a remote filter action and column widths for a remote table.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1172,"sizeBytes":5252},{"name":"16.remote-column-sorting.md","path":"packages/data-table/docs/8.examples/16.remote-column-sorting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/8.examples/16.remote-column-sorting.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Remote Column Sorting\ndescription: Use shadcn header buttons to send sort params to a remote data model.\nsidebar:\n  label: Remote Sorting\n---\n\nEach sortable column header sends a `sort` action to the remote model. The action updates the request params, the model refetches from the first page, and the active header button reads the current sort from `modelActionState$`. Do not keep a second `sortBy` / `sortOrder` React state just to paint the header; seed the model with `initialActions` for a default sort and let `modelActionState$` be the source of truth.\n\nThe shadcn `SortHeaderButton` defaults to a payload shaped as `{ field, direction }`, where `direction` cycles through `'asc'`, `'desc'`, and no sort.\n\nInstall the optional sort button wrapper before importing it:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-sort`.\n\n## APIs used\n\n- `remoteModel()` — refetches rows when request params change\n- `SortHeaderButton` — shadcn slot component for column-header sorting\n- `HeaderEnd` — places the sort button after the header label\n- `computeRowKey` — keeps row identity stable when sorting reorders rows\n- `initialActions` — seeds the active sort in model action state\n- `modelStatePersistenceAdapter()` — optionally persists the sort action payload across reloads\n\n```tsx live wide file=App.tsx\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEnd } from '@/components/ui/data-table'\nimport { SortHeaderButton } from '@/components/ui/data-table/column-sort'\nimport { defaultOffsetViewportHandler, remoteModel } from '@virtuoso.dev/data-table'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nimport { fetchProducts, placeholder } from './api'\n\nimport type { Product, Query } from './api'\n\nconst defaultSort = { field: 'name', direction: 'asc' } satisfies NonNullable<Query['sort']>\nconst persistenceAdapters = [modelStatePersistenceAdapter()]\n\nexport default function App() {\n  const [model] = useState(() =>\n    remoteModel<Product, Query>({\n      actions: {\n        sort: {\n          persistence: true,\n          strategy: 'supersede',\n          handler: ({ params, payload }) => ({ ...params, sort: payload as Query['sort'] }),\n        },\n      },\n      fetch: fetchProducts,\n      initialActions: [{ action: 'sort', payload: defaultSort }],\n      initialParams: { sort: defaultSort },\n      onViewportChange: defaultOffsetViewportHandler,\n      pageSize: 40,\n      placeholder,\n    })\n  )\n\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 420 }}>\n      <DataTableStatePersistence adapters={persistenceAdapters} storageKey=\"remote-column-sorting-example\" />\n\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ row }) => row.data.name}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEnd component={SortHeaderButton} />\n          {() => 'Price'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => `$${cellValue}`}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=api.ts\nimport type { FetchParams } from '@virtuoso.dev/data-table'\n\nexport interface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  price: number\n}\n\nexport interface Query {\n  sort?: {\n    field: 'name' | 'category' | 'price'\n    direction: 'asc' | 'desc'\n  }\n}\n\nconst categories: Product['category'][] = ['Office', 'Peripherals', 'Audio']\n\nconst allProducts: Product[] = Array.from({ length: 240 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `${categories[index % categories.length]} Item ${index + 1}`,\n  category: categories[index % categories.length]!,\n  price: 49 + (index % 11) * 13,\n}))\n\nexport const placeholder: Product = {\n  id: 'loading',\n  name: 'Loading...',\n  category: 'Office',\n  price: 0,\n}\n\nasync function pause(ms: number, signal: AbortSignal) {\n  await new Promise<void>((resolve, reject) => {\n    const timer = window.setTimeout(resolve, ms)\n    signal.addEventListener(\n      'abort',\n      () => {\n        window.clearTimeout(timer)\n        reject(new Error('aborted'))\n      },\n      { once: true }\n    )\n  })\n}\n\nfunction compareProducts(sort: NonNullable<Query['sort']>) {\n  return (left: Product, right: Product) => {\n    const leftValue = left[sort.field]\n    const rightValue = right[sort.field]\n    const result =\n      typeof leftValue === 'number' && typeof rightValue === 'number'\n        ? leftValue - rightValue\n        : String(leftValue).localeCompare(String(rightValue), undefined, { numeric: true })\n\n    return sort.direction === 'asc' ? result : -result\n  }\n}\n\nexport async function fetchProducts(params: FetchParams<Query>) {\n  await pause(180, params.signal)\n  const data = params.params.sort ? allProducts.toSorted(compareProducts(params.params.sort)) : allProducts\n\n  return {\n    rows: data.slice(params.offset, params.offset + params.limit),\n    totalCount: data.length,\n  }\n}\n```\n\n## When this doesn't fit\n\nIf your API uses a different query shape, render the button manually in `HeaderEnd` and pass `action`, `field`, `getDirection`, or `getPayload` props to `SortHeaderButton`. Copy the installed component into your app-specific table wrapper when every table in the app should share the same sort payload shape.\n","frontmatter":{"title":"Remote Column Sorting","description":"Use shadcn header buttons to send sort params to a remote data model.","sidebar":{"label":"Remote Sorting"}},"isInternal":false,"tokens":1429,"sizeBytes":6164},{"name":"01.testing.md","path":"packages/data-table/docs/9.guides/01.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/9.guides/01.testing.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Testing\ndescription: Make table behavior deterministic in tests by providing explicit test dimensions.\nsidebar:\n  label: Testing\n---\n\n`VirtuosoDataTableTestingContext` hands the table fixed `itemHeight` and `viewportHeight` values so virtualization decisions don't depend on real layout. Use it in component tests that assert visible rows, loaded ranges, or loading/empty state.\n\nIn browser tests (Playwright, real layout), prefer the actual measurements — only reach for the testing context when layout is missing or timing-dependent.\n\n## Fixed-size test table\n\n```tsx live\nimport { localModel, VirtuosoDataTableTestingContext } from '@virtuoso.dev/data-table'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nconst rows = Array.from({ length: 20 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      <DataTable className=\"rounded-xl\" model={model} style={{ height: 240 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n`itemHeight` is the row height the table assumes; `viewportHeight` is the visible vertical space. In this example, 240px ÷ 40px = six rows before overscan.\n\nThe table still needs a real height in its own layout. Match the provider dimensions and the table height unless the test specifically targets a mismatch.\n\n## What to assert\n\nAssert behavior, not exact DOM node counts — overscan, sticky headers, and measurement buffers can render extra rows.\n\nGood browser-test assertions:\n\n- a row near the top is visible before scrolling\n- a target row becomes visible after `scrollToRow$` or user scrolling\n- an empty placeholder is visible when the model has no rows\n- a remote loading overlay appears while a request is pending\n\nFor component tests, wrap the table once in a shared helper:\n\n```tsx\nfunction renderTestTable(children: React.ReactNode) {\n  return render(\n    <VirtuosoDataTableTestingContext.Provider value={{ itemHeight: 40, viewportHeight: 240 }}>\n      {children}\n    </VirtuosoDataTableTestingContext.Provider>\n  )\n}\n```\n\n## Browser vs. component tests\n\n- Sticky columns, resize handles, drag interactions, custom scroll parents — test in the browser with real layout.\n- Visible-row and loading-state assertions — use `VirtuosoDataTableTestingContext` in component tests.\n- Keep data generation simple. Behavior is what you're validating, not visual polish.\n","frontmatter":{"title":"Testing","description":"Make table behavior deterministic in tests by providing explicit test dimensions.","sidebar":{"label":"Testing"}},"isInternal":false,"tokens":631,"sizeBytes":2858},{"name":"02.performance.md","path":"packages/data-table/docs/9.guides/02.performance.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/9.guides/02.performance.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F02.performance.md -->","frontmatter":{"title":"Performance","description":"Keep render work predictable with stable keys, deliberate overscan, and bounded viewport rendering.","sidebar":{"label":"Performance"}},"isInternal":false,"tokens":734,"sizeBytes":3669},{"name":"03.troubleshooting.md","path":"packages/data-table/docs/9.guides/03.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/9.guides/03.troubleshooting.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F03.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting","description":"Diagnose the most common integration issues around layout, imports, and scroll configuration.","sidebar":{"label":"Troubleshooting"}},"isInternal":false,"tokens":788,"sizeBytes":4194},{"name":"04.migrating-from-table-virtuoso.md","path":"packages/data-table/docs/9.guides/04.migrating-from-table-virtuoso.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/9.guides/04.migrating-from-table-virtuoso.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","frontmatter":{"title":"Migrating From TableVirtuoso","description":"Map common TableVirtuoso patterns to the data-table package.","sidebar":{"label":"Migrating From TableVirtuoso"}},"isInternal":false,"tokens":890,"sizeBytes":3935},{"name":"05.debug-instrumentation.md","path":"packages/data-table/docs/9.guides/05.debug-instrumentation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/docs/9.guides/05.debug-instrumentation.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fdocs%2F9.guides%2F05.debug-instrumentation.md -->","frontmatter":{"title":"Inspecting Row Re-renders","description":"Enable unstable row render events while investigating rendering behavior.","sidebar":{"label":"Row Renders"}},"isInternal":false,"tokens":1203,"sizeBytes":5047},{"name":"10.smooth-scrolling.md","path":"packages/message-list/docs/10.smooth-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/10.smooth-scrolling.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F10.smooth-scrolling.md -->","frontmatter":{"id":"virtuoso-message-list-smooth-scrolling","title":"Virtuoso Message Custom Smooth Scrolling","description":"Customize scroll animations with custom easing functions like bounce or elastic for unique chat UX.","sidebar_label":"Custom Smooth Scrolling","sidebar_position":8,"slug":"/virtuoso-message-list/smooth-scrolling"},"isInternal":false,"tokens":1403,"sizeBytes":5608},{"name":"11.testing.md","path":"packages/message-list/docs/11.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/11.testing.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F11.testing.md -->","frontmatter":{"id":"virtuoso-message-list-testing","title":"Virtuoso Message List Testing","description":"Test VirtuosoMessageList with Playwright E2E or React Testing Library using VirtuosoMessageListTestingContext.","sidebar_label":"Testing","sidebar_position":9,"slug":"/virtuoso-message-list/testing"},"isInternal":false,"tokens":478,"sizeBytes":2081},{"name":"15.imperative-data-api.md","path":"packages/message-list/docs/15.imperative-data-api.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/15.imperative-data-api.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F15.imperative-data-api.md -->","frontmatter":{"id":"virtuoso-message-list-working-with-data","title":"Virtuoso Message List - Working with Data","description":"Use imperative data API for append, prepend, map, and delete operations with automatic scroll handling.","sidebar_label":"Imperative Data API","sidebar_position":10,"slug":"/virtuoso-message-list/working-with-data"},"isInternal":false,"tokens":3508,"sizeBytes":15613},{"name":"19.resize-observer-errors.md","path":"packages/message-list/docs/19.resize-observer-errors.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/19.resize-observer-errors.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F19.resize-observer-errors.md -->","frontmatter":{"id":"virtuoso-message-list-resize-observer-errors","title":"Virtuoso Message List Resize Observer Errors","description":"Handle benign ResizeObserver loop errors in Webpack dev server and Sentry error tracking.","sidebar_label":"Resize Observer Errors","sidebar_position":10,"slug":"/virtuoso-message-list/resize-observer-errors"},"isInternal":false,"tokens":333,"sizeBytes":1609},{"name":"01.intro.md","path":"packages/message-list/docs/2.tutorial/01.intro.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/01.intro.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F01.intro.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-intro","title":"Virtuoso Message List Tutorial - Intro","description":"Get started building a chat UI with VirtuosoMessageList - set up the project and define message data structures.","sidebar_label":"Intro","sidebar_position":1,"slug":"/virtuoso-message-list/tutorial/intro"},"isInternal":false,"tokens":865,"sizeBytes":3697},{"name":"02.message-list.md","path":"packages/message-list/docs/2.tutorial/02.message-list.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/02.message-list.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F02.message-list.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-message-list","title":"Virtuoso Message List Tutorial - Message List","description":"Add the VirtuosoMessageList component to your app with custom ItemContent and context for chat message rendering.","sidebar_label":"Message List","sidebar_position":2,"slug":"/virtuoso-message-list/tutorial/message-list"},"isInternal":false,"tokens":2551,"sizeBytes":11018},{"name":"03.loading-older-messages.md","path":"packages/message-list/docs/2.tutorial/03.loading-older-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/03.loading-older-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F03.loading-older-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-loading-older-messages","title":"Virtuoso Message List Tutorial - Loading Older Messages","description":"Implement infinite scroll to load chat history when users scroll up using onScroll and prepend scroll modifier.","sidebar_label":"Loading Older Messages","sidebar_position":3,"slug":"/virtuoso-message-list/tutorial/loading-older-messages"},"isInternal":false,"tokens":877,"sizeBytes":3862},{"name":"04.scroll-to-bottom-button.md","path":"packages/message-list/docs/2.tutorial/04.scroll-to-bottom-button.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/04.scroll-to-bottom-button.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-scroll-to-bottom-button","title":"Virtuoso Message List Tutorial - Scroll to Bottom Button","description":"Add a scroll-to-bottom button using StickyFooter, useVirtuosoLocation and useVirtuosoMethods hooks.","sidebar_label":"Scroll to Bottom Button","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/scroll-to-bottom-button"},"isInternal":false,"tokens":701,"sizeBytes":3040},{"name":"05.receive-messages.md","path":"packages/message-list/docs/2.tutorial/05.receive-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/05.receive-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F05.receive-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-receive-messages","title":"Virtuoso Message List Tutorial - Receive Messages","description":"Handle incoming messages with auto-scroll-to-bottom modifier and display unread message indicators.","sidebar_label":"Receive Messages","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/receive-messages"},"isInternal":false,"tokens":1097,"sizeBytes":4957},{"name":"06.send-messages.md","path":"packages/message-list/docs/2.tutorial/06.send-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/06.send-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F06.send-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-send-messages","title":"Virtuoso Message List Tutorial - Send Messages","description":"Implement message sending with optimistic updates and delivery confirmation in VirtuosoMessageList.","sidebar_label":"Send Messages","sidebar_position":6,"slug":"/virtuoso-message-list/tutorial/send-messages"},"isInternal":false,"tokens":627,"sizeBytes":3016},{"name":"07.multiple-channels.md","path":"packages/message-list/docs/2.tutorial/07.multiple-channels.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/2.tutorial/07.multiple-channels.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F2.tutorial%2F07.multiple-channels.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-multiple-channels","title":"Virtuoso Message List Tutorial - Multiple Channels","description":"Support multiple chat channels with independent scroll positions and message history in VirtuosoMessageList.","sidebar_label":"Multiple Channels","sidebar_position":7,"slug":"/virtuoso-message-list/tutorial/multiple-channels"},"isInternal":false,"tokens":625,"sizeBytes":2848},{"name":"20.scroll-modifier.md","path":"packages/message-list/docs/20.scroll-modifier.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/20.scroll-modifier.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F20.scroll-modifier.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-modifier","title":"Virtuoso Message List Scroll Position Control","description":"Control VirtuosoMessageList scroll behavior with modifiers for prepend, append, item changes, and initial position.","sidebar_label":"Scroll Modifier","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-modifier"},"isInternal":false,"tokens":1563,"sizeBytes":7197},{"name":"21.controlled-mode-recipes.md","path":"packages/message-list/docs/21.controlled-mode-recipes.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/21.controlled-mode-recipes.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F21.controlled-mode-recipes.md -->","frontmatter":{"id":"virtuoso-message-list-controlled-mode-recipes","title":"Virtuoso Message List Controlled Mode Recipes","description":"Organize controlled message-list state behind app-owned actions for local sends, incoming messages, streaming updates, and row growth.","sidebar_label":"Controlled Mode Recipes","sidebar_position":11,"slug":"/virtuoso-message-list/controlled-mode-recipes"},"isInternal":false,"tokens":1348,"sizeBytes":6129},{"name":"01.messaging.md","path":"packages/message-list/docs/3.examples/01.messaging.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/01.messaging.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F01.messaging.md -->","frontmatter":{"id":"virtuoso-message-list-examples-messaging","title":"Virtuoso Message List Examples - Messaging","description":"Complete chat messaging UI example with send, receive, optimistic updates, and multi-channel support.","sidebar_label":"Messaging Interface","sidebar_position":1,"slug":"/virtuoso-message-list/examples/messaging"},"isInternal":false,"tokens":2409,"sizeBytes":10356},{"name":"02.ai-chatbot.md","path":"packages/message-list/docs/3.examples/02.ai-chatbot.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/02.ai-chatbot.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F02.ai-chatbot.md -->","frontmatter":{"id":"virtuoso-message-list-examples-ai-chatbot","title":"Virtuoso Message List Examples - AI Chatbot","description":"Build AI chatbot interfaces with streaming responses and smooth auto-scroll using VirtuosoMessageList.","sidebar_label":"AI Chatbot","sidebar_position":2,"slug":"/virtuoso-message-list/examples/ai-chatbot"},"isInternal":false,"tokens":1015,"sizeBytes":4504},{"name":"03.gemini.md","path":"packages/message-list/docs/3.examples/03.gemini.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/03.gemini.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F03.gemini.md -->","frontmatter":{"id":"virtuoso-message-list-examples-gemini","title":"Virtuoso Message List Examples - Gemini-like Chatbot Scroll","description":"Implement Gemini-style chat scroll where questions align to top with streaming responses below.","sidebar_label":"Gemini-like Chatbot Scroll","sidebar_position":3,"slug":"/virtuoso-message-list/examples/gemini"},"isInternal":false,"tokens":1032,"sizeBytes":4584},{"name":"04.reactions.md","path":"packages/message-list/docs/3.examples/04.reactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/04.reactions.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F04.reactions.md -->","frontmatter":{"id":"virtuoso-message-list-examples-reactions","title":"Virtuoso Message List Examples - Reactions","description":"Add emoji reactions to chat messages with proper scroll position handling using items-change modifier.","sidebar_label":"Reactions","sidebar_position":4,"slug":"/virtuoso-message-list/examples/reactions"},"isInternal":false,"tokens":1058,"sizeBytes":4392},{"name":"05.date-separators.md","path":"packages/message-list/docs/3.examples/05.date-separators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/05.date-separators.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F05.date-separators.md -->","frontmatter":{"id":"virtuoso-message-list-examples-date-separators","title":"Virtuoso Message List Examples - Date Separators","description":"Display date separators between messages with sticky headers using prevData prop and useCurrentlyRenderedData hook.","sidebar_label":"Date Separators","sidebar_position":5,"slug":"/virtuoso-message-list/examples/date-separators"},"isInternal":false,"tokens":1082,"sizeBytes":4462},{"name":"06.scroll-to-reply.md","path":"packages/message-list/docs/3.examples/06.scroll-to-reply.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/06.scroll-to-reply.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F06.scroll-to-reply.md -->","frontmatter":{"id":"virtuoso-message-list-examples-scroll-to-reply","title":"Virtuoso Message List Examples - Scroll to Reply","description":"Navigate to quoted messages with smooth scroll and highlight animation using scrollToItem and data.findIndex.","sidebar_label":"Scroll to Reply","sidebar_position":6,"slug":"/virtuoso-message-list/examples/scroll-to-reply"},"isInternal":false,"tokens":1330,"sizeBytes":5524},{"name":"07.grouped-messages.md","path":"packages/message-list/docs/3.examples/07.grouped-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/3.examples/07.grouped-messages.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F3.examples%2F07.grouped-messages.md -->","frontmatter":{"id":"virtuoso-message-list-examples-grouped-messages","title":"Virtuoso Message List Examples - Grouped Messages","description":"Group consecutive messages from the same user with connected bubbles using prevData and nextData props.","sidebar_label":"Grouped Messages","sidebar_position":7,"slug":"/virtuoso-message-list/examples/grouped-messages"},"isInternal":false,"tokens":1071,"sizeBytes":4720},{"name":"4.headers-footers.md","path":"packages/message-list/docs/4.headers-footers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/4.headers-footers.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F4.headers-footers.md -->","frontmatter":{"id":"virtuoso-message-list-headers-footers","title":"Virtuoso Message List Headers and Footers","description":"Add sticky and scrollable headers and footers to VirtuosoMessageList for loading indicators and overlays.","sidebar_label":"Headers and Footers","sidebar_position":5,"slug":"/virtuoso-message-list/headers-footers"},"isInternal":false,"tokens":1426,"sizeBytes":5588},{"name":"5.context.md","path":"packages/message-list/docs/5.context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/5.context.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F5.context.md -->","frontmatter":{"id":"virtuoso-message-list-context","title":"Virtuoso Message List Context","description":"Pass shared state to ItemContent and custom components without prop drilling using the context prop.","sidebar_label":"Context","sidebar_position":6,"slug":"/virtuoso-message-list/context"},"isInternal":false,"tokens":567,"sizeBytes":2477},{"name":"6.item-keys.md","path":"packages/message-list/docs/6.item-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/6.item-keys.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F6.item-keys.md -->","frontmatter":{"id":"virtuoso-message-list-item-keys","title":"Virtuoso Message List Item Keys","description":"Generate stable React keys for message list items using computeItemKey to prevent unnecessary remounts.","sidebar_label":"Item Keys","sidebar_position":6,"slug":"/virtuoso-message-list/item-keys"},"isInternal":false,"tokens":498,"sizeBytes":2187},{"name":"7.hooks.md","path":"packages/message-list/docs/7.hooks.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/7.hooks.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F7.hooks.md -->","frontmatter":{"id":"virtuoso-message-list-hooks","title":"Virtuoso Message List Hooks","description":"Access message list methods and scroll state from child components using useVirtuosoMethods and useVirtuosoLocation.","sidebar_label":"Hooks","sidebar_position":7,"slug":"/virtuoso-message-list/hooks"},"isInternal":false,"tokens":496,"sizeBytes":2175},{"name":"8.custom-scrollbar.md","path":"packages/message-list/docs/8.custom-scrollbar.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/8.custom-scrollbar.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F8.custom-scrollbar.md -->","frontmatter":{"id":"virtuoso-message-list-custom-scrollbar","title":"Virtuoso Message List Custom Scrollbar","description":"Integrate SimpleBar and other custom scrollbar libraries with VirtuosoMessageList using customScrollParent.","sidebar_label":"Custom Scrollbar Component","sidebar_position":30,"slug":"/virtuoso-message-list/custom-scrollbar"},"isInternal":false,"tokens":345,"sizeBytes":1487},{"name":"80.licensing.md","path":"packages/message-list/docs/80.licensing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/80.licensing.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F80.licensing.md -->","frontmatter":{"id":"virtuoso-message-list-licensing","title":"Virtuoso Message List Licensing","description":"Commercial license options, seat calculation, key setup, and trial terms for Virtuoso Message List.","sidebar_label":"Licensing","sidebar_position":3,"slug":"/virtuoso-message-list/licensing"},"isInternal":false,"tokens":1281,"sizeBytes":6112},{"name":"9.scrolling-to-item.md","path":"packages/message-list/docs/9.scrolling-to-item.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/docs/9.scrolling-to-item.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2Fdocs%2F9.scrolling-to-item.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-to-item","title":"Virtuoso Message List Scroll to Item","description":"Programmatically scroll to specific messages using the imperative scrollToItem API with alignment options.","sidebar_label":"Scroll to Item","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-to-item"},"isInternal":false,"tokens":373,"sizeBytes":1553},{"name":"auto-resizing.md","path":"packages/react-virtuoso/docs/1.virtuoso/auto-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/auto-resizing.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fauto-resizing.md -->","frontmatter":{"title":"Auto Resizing Virtual List","description":"React Virtuoso automatically handles dynamic item height changes from content resizing, images loading, and container resizing.","sidebar":{"label":"Auto Resizing","order":2}},"isInternal":false,"tokens":341,"sizeBytes":1404},{"name":"basic-usage.md","path":"packages/react-virtuoso/docs/1.virtuoso/basic-usage.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/basic-usage.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fbasic-usage.md -->","frontmatter":{"title":"Virtual List With 100,000 Items","description":"Get started with React Virtuoso by rendering a virtual list with 100,000 items of unknown sizes.","sidebar":{"label":"100,000 Items","order":1}},"isInternal":false,"tokens":590,"sizeBytes":2374},{"name":"custom-scroll-container.md","path":"packages/react-virtuoso/docs/1.virtuoso/custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/custom-scroll-container.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustom-scroll-container.md -->","frontmatter":{"title":"Customize Root Element","description":"Customize the React Virtuoso scroller element with custom styling, DOM event handlers, or integrate with custom scrollbar libraries.","position":500,"sidebar":{"label":"Customize Scroller"}},"isInternal":false,"tokens":487,"sizeBytes":2020},{"name":"customize-rendering.md","path":"packages/react-virtuoso/docs/1.virtuoso/customize-rendering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/customize-rendering.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fcustomize-rendering.md -->","frontmatter":{"title":"Customize Rendering","description":"Replace React Virtuoso's default DOM elements with custom components for full control over list rendering and styling.","position":501,"sidebar":{"label":"Customize Rendering"}},"isInternal":false,"tokens":604,"sizeBytes":2500},{"name":"endless-scrolling.md","path":"packages/react-virtuoso/docs/1.virtuoso/endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/endless-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fendless-scrolling.md -->","frontmatter":{"title":"Endless Scrolling","description":"The React Virtuoso component makes it trivial to implement infinite scrolling lists in both directions with variably sized items.","sidebar":{"label":"Endless Scrolling","order":100}},"isInternal":false,"tokens":383,"sizeBytes":1584},{"name":"footer.md","path":"packages/react-virtuoso/docs/1.virtuoso/footer.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/footer.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ffooter.md -->","frontmatter":{"title":"List with Footer Example","description":"Add a custom footer component to React Virtuoso lists for loading indicators, load more buttons, or end-of-list messages.","sidebar":{"label":"Footer","order":5}},"isInternal":false,"tokens":316,"sizeBytes":1317},{"name":"horizontal-mode.md","path":"packages/react-virtuoso/docs/1.virtuoso/horizontal-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/horizontal-mode.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fhorizontal-mode.md -->","frontmatter":{"title":"Horizontal Mode","description":"Render React Virtuoso lists horizontally with the horizontalDirection property for carousels and horizontal scrolling UIs.","sidebar_title":"Horizontal Mode","sidebar":{"order":90}},"isInternal":false,"tokens":321,"sizeBytes":1353},{"name":"initial-index.md","path":"packages/react-virtuoso/docs/1.virtuoso/initial-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/initial-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Finitial-index.md -->","frontmatter":{"title":"Start from a certain item","description":"Use initialTopMostItemIndex to start React Virtuoso lists at a specific item position on mount.","sidebar":{"label":"Initial Index","order":4}},"isInternal":false,"tokens":222,"sizeBytes":903},{"name":"keyboard-navigation.md","path":"packages/react-virtuoso/docs/1.virtuoso/keyboard-navigation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/keyboard-navigation.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fkeyboard-navigation.md -->","frontmatter":{"title":"Keyboard Navigation","description":"Implement keyboard navigation in React Virtuoso lists using the scrollIntoView method with arrow key support.","sidebar":{"label":"Keyboard Navigation","order":121}},"isInternal":false,"tokens":556,"sizeBytes":2337},{"name":"press-to-load-more.md","path":"packages/react-virtuoso/docs/1.virtuoso/press-to-load-more.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/press-to-load-more.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fpress-to-load-more.md -->","frontmatter":{"title":"Press to Load More","description":"Add a load more button to React Virtuoso lists for user-triggered pagination instead of automatic infinite scrolling.","sidebar":{"label":"Press to Load More","order":101}},"isInternal":false,"tokens":536,"sizeBytes":2153},{"name":"range-change-callback.md","path":"packages/react-virtuoso/docs/1.virtuoso/range-change-callback.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/range-change-callback.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Frange-change-callback.md -->","frontmatter":{"title":"Range Change Callback","description":"Track visible item ranges in React Virtuoso lists using the rangeChanged callback for analytics or lazy loading.","sidebar":{"label":"Range Change Callback","order":101}},"isInternal":false,"tokens":297,"sizeBytes":1189},{"name":"scroll-handling.md","path":"packages/react-virtuoso/docs/1.virtuoso/scroll-handling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/scroll-handling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-handling.md -->","frontmatter":{"title":"Scroll Handling","description":"Optimize React Virtuoso scroll performance by detecting scroll state and replacing heavy content with placeholders.","sidebar":{"label":"Scroll Handling","order":302}},"isInternal":false,"tokens":459,"sizeBytes":1879},{"name":"scroll-seek-placeholders.md","path":"packages/react-virtuoso/docs/1.virtuoso/scroll-seek-placeholders.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/scroll-seek-placeholders.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","frontmatter":{"title":"Scroll Seek Placeholders","description":"Improve React Virtuoso fast scrolling performance by showing lightweight placeholders during rapid scroll.","sidebar":{"label":"Scroll Seek Placeholders","order":502}},"isInternal":false,"tokens":572,"sizeBytes":2458},{"name":"scroll-to-index.md","path":"packages/react-virtuoso/docs/1.virtuoso/scroll-to-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/scroll-to-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fscroll-to-index.md -->","frontmatter":{"title":"Scroll to Index","description":"Programmatically scroll to any item in React Virtuoso using scrollToIndex with alignment and smooth scroll options.","sidebar":{"label":"Scroll to Index","order":50}},"isInternal":false,"tokens":650,"sizeBytes":2892},{"name":"tailwind-test.md","path":"packages/react-virtuoso/docs/1.virtuoso/tailwind-test.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/tailwind-test.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftailwind-test.md -->","frontmatter":{"title":"Tailwind Test"},"isInternal":false,"tokens":592,"sizeBytes":2316},{"name":"top-items.md","path":"packages/react-virtuoso/docs/1.virtuoso/top-items.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/top-items.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Ftop-items.md -->","frontmatter":{"title":"Top Items List Example","description":"Pin the first N items at the top of React Virtuoso lists with the topItemCount property for sticky headers.","sidebar":{"label":"Top Items"}},"isInternal":false,"tokens":197,"sizeBytes":801},{"name":"window-scrolling.md","path":"packages/react-virtuoso/docs/1.virtuoso/window-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/1.virtuoso/window-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F1.virtuoso%2Fwindow-scrolling.md -->","frontmatter":{"title":"Window Scrolling","description":"Use the browser window as the scroll container for React Virtuoso lists with the useWindowScroll property.","sidebar":{"label":"Window Scrolling","order":250}},"isInternal":false,"tokens":143,"sizeBytes":543},{"name":"grouped-by-first-letter.md","path":"packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-by-first-letter.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-by-first-letter.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","frontmatter":{"title":"Grouped by First Letter","description":"Build alphabetically grouped lists with sticky headers using GroupedVirtuoso for contact lists and directories.","sidebar":{"label":"Grouped by First Letter","order":2}},"isInternal":false,"tokens":463,"sizeBytes":1946},{"name":"grouped-numbers.md","path":"packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-numbers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-numbers.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","frontmatter":{"title":"Grouped 10,000 numbers","description":"Display large datasets in groups with sticky headers using React Virtuoso GroupedVirtuoso component.","sidebar":{"label":"Grouped Numbers","order":1}},"isInternal":false,"tokens":244,"sizeBytes":921},{"name":"grouped-with-load-on-demand.md","path":"packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-with-load-on-demand.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/2.grouped-virtuoso/grouped-with-load-on-demand.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","frontmatter":{"title":"Grouped Load on Demand","description":"Implement infinite scrolling with grouped data in React Virtuoso by dynamically loading and appending groups.","sidebar":{"label":"Load on Demand","order":4}},"isInternal":false,"tokens":763,"sizeBytes":3281},{"name":"scroll-to-group.md","path":"packages/react-virtuoso/docs/2.grouped-virtuoso/scroll-to-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/2.grouped-virtuoso/scroll-to-group.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","frontmatter":{"title":"Scroll to Group","description":"Build iOS-style contact lists with alphabetical navigation and scroll-to-group functionality in React Virtuoso.","sidebar":{"label":"Scroll to Group","order":5}},"isInternal":false,"tokens":624,"sizeBytes":2836},{"name":"grid-responsive-columns.md","path":"packages/react-virtuoso/docs/3.virtuoso-grid/grid-responsive-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/3.virtuoso-grid/grid-responsive-columns.md","title":"3.virtuoso-grid Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","frontmatter":{"title":"Grid with Responsive Columns","description":"Build responsive virtualized grids with VirtuosoGrid using CSS flexbox or grid for adaptive column layouts.","sidebar":{"label":"Responsive Columns"}},"isInternal":false,"tokens":593,"sizeBytes":2456},{"name":"basic-table.md","path":"packages/react-virtuoso/docs/4.table-virtuoso/basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/4.table-virtuoso/basic-table.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Fbasic-table.md -->","frontmatter":{"title":"Table Virtuoso Basic Example","description":"Virtualize large HTML tables with unknown row heights using TableVirtuoso for efficient rendering.","position":1,"sidebar":{"label":"Basic Example"}},"isInternal":false,"tokens":742,"sizeBytes":3007},{"name":"table-fixed-columns.md","path":"packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-columns.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Columns","description":"Create horizontally scrollable tables with sticky frozen columns using TableVirtuoso and CSS positioning.","sidebar":{"label":"Fixed Columns"}},"isInternal":false,"tokens":431,"sizeBytes":1762},{"name":"table-fixed-headers.md","path":"packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-headers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/4.table-virtuoso/table-fixed-headers.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Headers","description":"Add sticky table headers that remain visible while scrolling using TableVirtuoso's fixedHeaderContent prop.","sidebar":{"label":"Fixed Headers"}},"isInternal":false,"tokens":307,"sizeBytes":1281},{"name":"table-grouped.md","path":"packages/react-virtuoso/docs/4.table-virtuoso/table-grouped.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/4.table-virtuoso/table-grouped.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F4.table-virtuoso%2Ftable-grouped.md -->","frontmatter":{"title":"Table Virtuoso Example with Grouped Rows","description":"Display grouped table data with sticky group headers using GroupedTableVirtuoso for organized data presentation.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":400,"sizeBytes":1621},{"name":"material-ui-endless-scrolling.md","path":"packages/react-virtuoso/docs/5.third-party-integration/material-ui-endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/5.third-party-integration/material-ui-endless-scrolling.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","frontmatter":{"title":"MUI List with Endless Scrolling","description":"Use React Virtuoso with Material UI List components for virtualized grouped lists with MUI styling.","sidebar":{"label":"MUI List","order":1}},"isInternal":false,"tokens":255,"sizeBytes":1140},{"name":"mocking-in-tests.md","path":"packages/react-virtuoso/docs/5.third-party-integration/mocking-in-tests.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/5.third-party-integration/mocking-in-tests.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmocking-in-tests.md -->","frontmatter":{"title":"Mocking in tests","description":"Test React Virtuoso components in Jest and Testing Library using VirtuosoMockContext to simulate DOM measurements.","sidebar":{"label":"Mocking in tests"}},"isInternal":false,"tokens":621,"sizeBytes":2380},{"name":"mui-table-virtual-scroll.md","path":"packages/react-virtuoso/docs/5.third-party-integration/mui-table-virtual-scroll.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/5.third-party-integration/mui-table-virtual-scroll.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","frontmatter":{"title":"Table Virtuoso with MUI Table","description":"Integrate React Virtuoso TableVirtuoso with Material UI Table components for virtualized data grids.","sidebar":{"label":"MUI Table","order":1}},"isInternal":false,"tokens":316,"sizeBytes":1329},{"name":"tanstack-table-integration.md","path":"packages/react-virtuoso/docs/5.third-party-integration/tanstack-table-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/5.third-party-integration/tanstack-table-integration.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F5.third-party-integration%2Ftanstack-table-integration.md -->","frontmatter":{"title":"Table Virtuoso integrated with @tanstack/table","description":"Combine TanStack Table's sorting and filtering with TableVirtuoso's virtualization for large datasets.","sidebar":{"label":"@tanstack/table"}},"isInternal":false,"tokens":260,"sizeBytes":1170},{"name":"6.troubleshooting.md","path":"packages/react-virtuoso/docs/6.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/docs/6.troubleshooting.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2Fdocs%2F6.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting React Virtuoso","description":"Fix common React Virtuoso issues including margin problems, scroll jumping, zero-sized elements, and testing errors.","sidebar":{"label":"Troubleshooting","order":99}},"isInternal":false,"tokens":1291,"sizeBytes":5606},{"name":"settings.local.json","path":"packages/reactive-engine-core/.claude/settings.local.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/.claude/settings.local.json","title":".claude Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2F.claude%2Fsettings.local.json -->","frontmatter":{"permissions":{"allow":["mcp__serena__get_symbols_overview","mcp__serena__find_symbol","mcp__serena__insert_after_symbol","mcp__serena__replace_symbol_body","Bash(pnpm typecheck:*)","Bash(pnpm format:*)","Bash(pnpm lint:*)","Bash(pnpm build:*)"]}},"isInternal":false,"tokens":105,"sizeBytes":324},{"name":"01.core-concepts.md","path":"packages/reactive-engine-core/docs/01.core-concepts.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/01.core-concepts.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F01.core-concepts.md -->","frontmatter":{"title":"Core Concepts","description":"Nodes, engines, and the separation between graph definitions and runtime state."},"isInternal":false,"tokens":1756,"sizeBytes":8056},{"name":"02.engine-and-lifecycle.md","path":"packages/reactive-engine-core/docs/02.engine-and-lifecycle.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/02.engine-and-lifecycle.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F02.engine-and-lifecycle.md -->","frontmatter":{"title":"Engine and Lifecycle","description":"How engines activate nodes, the three API flavors, engine context, child engines, and disposal."},"isInternal":false,"tokens":1938,"sizeBytes":9457},{"name":"03.transactions.md","path":"packages/reactive-engine-core/docs/03.transactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/03.transactions.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F03.transactions.md -->","frontmatter":{"title":"Transactions and Execution","description":"How publishing works - transactional cycles, topological ordering, and how the engine differs from RxJS."},"isInternal":false,"tokens":1312,"sizeBytes":6271},{"name":"04.operators-and-combinators.md","path":"packages/reactive-engine-core/docs/04.operators-and-combinators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/04.operators-and-combinators.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F04.operators-and-combinators.md -->","frontmatter":{"title":"Operators and Combinators","description":"Reference for transforming and connecting nodes."},"isInternal":false,"tokens":1651,"sizeBytes":8877},{"name":"05.building-on-the-engine.md","path":"packages/reactive-engine-core/docs/05.building-on-the-engine.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/05.building-on-the-engine.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F05.building-on-the-engine.md -->","frontmatter":{"title":"Building on the Engine","description":"Architecture patterns for components and libraries built on the reactive engine, extracted from @virtuoso.dev/data-table."},"isInternal":false,"tokens":1670,"sizeBytes":7803},{"name":"06.diagnostics.md","path":"packages/reactive-engine-core/docs/06.diagnostics.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/docs/06.diagnostics.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2Fdocs%2F06.diagnostics.md -->","frontmatter":{"title":"Propagation Diagnostics","description":"Observe structured engine propagation without retaining application state or changing graph behavior."},"isInternal":false,"tokens":2093,"sizeBytes":11216},{"name":"01.queries-and-mutations.md","path":"packages/reactive-engine-query/docs/01.queries-and-mutations.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-query/docs/01.queries-and-mutations.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2Fdocs%2F01.queries-and-mutations.md -->","frontmatter":{"title":"Queries and Mutations","description":"Data fetching as nodes - lifecycle, result states, retries, and wiring patterns."},"isInternal":false,"tokens":1171,"sizeBytes":4787},{"name":"settings.local.json","path":"packages/reactive-engine-react/.claude/settings.local.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-react/.claude/settings.local.json","title":".claude Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2F.claude%2Fsettings.local.json -->","frontmatter":{"permissions":{"allow":["mcp__serena__find_symbol","mcp__serena__search_for_pattern","mcp__serena__think_about_task_adherence","Bash(pnpm typecheck:*)","Bash(pnpm lint:*)","Bash(pnpm test:*)","Bash(pnpm format:*)"]}},"isInternal":false,"tokens":94,"sizeBytes":286},{"name":"01.react-integration.md","path":"packages/reactive-engine-react/docs/01.react-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-react/docs/01.react-integration.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2Fdocs%2F01.react-integration.md -->","frontmatter":{"title":"React Integration","description":"EngineProvider lifecycle, hooks, and remote engine access."},"isInternal":false,"tokens":2482,"sizeBytes":12587},{"name":"01.routing.md","path":"packages/reactive-engine-router/docs/01.routing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-router/docs/01.routing.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2Fdocs%2F01.routing.md -->","frontmatter":{"title":"Routing","description":"Typed routes as nodes, navigation, layouts, guards, and browser history."},"isInternal":false,"tokens":883,"sizeBytes":3610},{"name":"01.storage-links.md","path":"packages/reactive-engine-storage/docs/01.storage-links.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-storage/docs/01.storage-links.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2Fdocs%2F01.storage-links.md -->","frontmatter":{"title":"Storage Links","description":"Persisting cells to localStorage, sessionStorage, and cookies."},"isInternal":false,"tokens":1242,"sizeBytes":5714},{"name":"CHANGELOG.md","path":"packages/virtuoso-skills/CHANGELOG.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/CHANGELOG.md","title":"Virtuoso-skills Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FCHANGELOG.md -->","isInternal":false,"tokens":629,"sizeBytes":2114},{"name":"package.json","path":"packages/virtuoso-skills/package.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/package.json","title":"@virtuoso.dev/virtuoso-skills","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fpackage.json -->","frontmatter":{"name":"@virtuoso.dev/virtuoso-skills","version":"0.2.1","private":true,"description":"Agent skill distribution for Virtuoso React virtualization packages.","type":"module","scripts":{"build":"tsx scripts/build-skills.ts","sync-version":"tsx scripts/sync-plugin-version.ts","version":"pnpm sync-version","validate":"pnpm validate:claude && pnpm validate:codex","validate:claude":"claude plugin validate . --strict","validate:codex":"tsx scripts/validate-codex-plugin.ts","check":"pnpm validate"},"devDependencies":{"@types/node":"catalog:","tsx":"4.23.1","typescript":"catalog:"}},"isInternal":false,"tokens":198,"sizeBytes":677},{"name":"01.shadcn.md","path":"packages/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","frontmatter":{"title":"Install With shadcn/ui","description":"Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.","sidebar":{"label":"Shadcn Install"}},"isInternal":false,"tokens":1344,"sizeBytes":5567},{"name":"02.headless.md","path":"packages/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","frontmatter":{"title":"Install The Headless Package","description":"Use the headless Virtuoso data table package directly when you need full control over styling and composition.","sidebar":{"label":"Headless Install"}},"isInternal":false,"tokens":963,"sizeBytes":3851},{"name":"01.local-data-model.md","path":"packages/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","frontmatter":{"title":"Local Data Model","description":"Use localModel for in-memory rows, derived views, persistence, and grouped data.","sidebar":{"label":"Local Data Model"}},"isInternal":false,"tokens":3273,"sizeBytes":13929},{"name":"02.remote-data-model.md","path":"packages/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","frontmatter":{"title":"Remote Data Model","description":"Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.","sidebar":{"label":"Remote Data Model"}},"isInternal":false,"tokens":2473,"sizeBytes":11459},{"name":"03.row-keys.md","path":"packages/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","frontmatter":{"title":"Row Keys","description":"Give the table stable row keys so React reconciles rows correctly when the model reorders them.","sidebar":{"label":"Row Keys"}},"isInternal":false,"tokens":676,"sizeBytes":3024},{"name":"index.md","path":"packages/virtuoso-skills/skills/data-table/references/2.data-model/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/2.data-model/index.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","frontmatter":{"title":"Choosing a Data Model","description":"Give the table a local or remote model before adding table features.","sidebar":{"label":"Choosing a Model"}},"isInternal":false,"tokens":598,"sizeBytes":2633},{"name":"01.defining-columns.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","frontmatter":{"title":"Defining Columns","description":"Declare the fields, order, defaults, and basic behavior of a data-table column.","sidebar":{"label":"Defining Columns"}},"isInternal":false,"tokens":1113,"sizeBytes":4842},{"name":"02.cell-and-header-renderers.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","frontmatter":{"title":"Formatting Cells and Headers","description":"Use render params to format cells and headers from column and row state.","sidebar":{"label":"Formatting Cells"}},"isInternal":false,"tokens":2511,"sizeBytes":11428},{"name":"03.column-groups.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","frontmatter":{"title":"Column Groups","description":"Organize related columns under shared headers.","sidebar":{"label":"Column Groups"}},"isInternal":false,"tokens":1609,"sizeBytes":7337},{"name":"04.sticky-columns.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns","description":"Pin high-value columns to the left or right side of a wide table.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1083,"sizeBytes":4250},{"name":"05.column-visibility.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","frontmatter":{"title":"Column Visibility","description":"Hide and restore columns with declarative defaults, reactive controls, and persistence.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":1857,"sizeBytes":8403},{"name":"06.column-resizing.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","frontmatter":{"title":"Column Resizing","description":"Let users change column widths with the opt-in resize model and shadcn resize handle.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":1888,"sizeBytes":8432},{"name":"07.column-reordering.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","frontmatter":{"title":"Column Reordering","description":"Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.","sidebar":{"label":"Column Reordering"}},"isInternal":false,"tokens":1822,"sizeBytes":7997},{"name":"08.runtime-columns.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","frontmatter":{"title":"Generating Columns at Runtime","description":"Discover and declare table columns when the field list is only known after data arrives.","sidebar":{"label":"Runtime Columns"}},"isInternal":false,"tokens":2071,"sizeBytes":9389},{"name":"09.column-layout.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","frontmatter":{"title":"Column Layout","description":"Set base column widths and choose which columns grow into extra horizontal space.","sidebar":{"label":"Column Layout"}},"isInternal":false,"tokens":988,"sizeBytes":4773},{"name":"index.md","path":"packages/virtuoso-skills/skills/data-table/references/3.columns/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/3.columns/index.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","frontmatter":{"title":"Overview","description":"Define the visible columns, headers, and cells that turn model rows into a grid.","sidebar":{"order":0,"label":"Overview"}},"isInternal":false,"tokens":1096,"sizeBytes":4897},{"name":"4.grouped-rows.md","path":"packages/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","frontmatter":{"title":"Grouped Rows","description":"Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":2792,"sizeBytes":11637},{"name":"5.state-persistence.md","path":"packages/virtuoso-skills/skills/data-table/references/5.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/5.state-persistence.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","frontmatter":{"title":"State Persistence","description":"Persist table feature state and model action state with opt-in adapters.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1898,"sizeBytes":8940},{"name":"6.controlling-the-table.md","path":"packages/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","frontmatter":{"title":"Controlling the Table","description":"Read table state and publish table actions from nearby toolbars, panels, and other application UI.","sidebar":{"label":"Controlling the Table"}},"isInternal":false,"tokens":2775,"sizeBytes":12134},{"name":"01.styling.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","frontmatter":{"title":"Styling Cells, Headers, and Rows","description":"Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.","sidebar":{"label":"Styling"}},"isInternal":false,"tokens":1218,"sizeBytes":5629},{"name":"02.replacing-internals.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","frontmatter":{"title":"Replacing Internals","description":"Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.","sidebar":{"label":"Replacing Internals"}},"isInternal":false,"tokens":1043,"sizeBytes":4738},{"name":"03.empty-and-loading-states.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","frontmatter":{"title":"Empty, Loading, and Error States","description":"Customize empty placeholders and remote loading states without changing data model.","sidebar":{"label":"Empty and Loading States"}},"isInternal":false,"tokens":815,"sizeBytes":3693},{"name":"04.scroll-containers.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","frontmatter":{"title":"Scroll Containers","description":"Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.","sidebar":{"label":"Scroll Containers"}},"isInternal":false,"tokens":1059,"sizeBytes":4654},{"name":"05.ambient-context.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","frontmatter":{"title":"Ambient Context","description":"A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.","sidebar":{"label":"Ambient Context"}},"isInternal":false,"tokens":860,"sizeBytes":4059},{"name":"06.header-slots.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","frontmatter":{"title":"Header Slots","description":"Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.","sidebar":{"label":"Header Slots"}},"isInternal":false,"tokens":1389,"sizeBytes":6260},{"name":"07.shadcn-wrapper.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","frontmatter":{"title":"Inside the shadcn Wrapper","description":"Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.","sidebar":{"label":"The shadcn Wrapper"}},"isInternal":false,"tokens":1570,"sizeBytes":7058},{"name":"08.card-mode.md","path":"packages/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","frontmatter":{"title":"Card Mode","description":"Switch the same data model between table rows and an automatically measured grid of equal-sized cards.","sidebar":{"label":"Card Mode"}},"isInternal":false,"tokens":1607,"sizeBytes":7492},{"name":"01.basic-table.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","frontmatter":{"title":"Basic Table Example","description":"Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.","sidebar":{"label":"Basic Table"}},"isInternal":false,"tokens":699,"sizeBytes":2983},{"name":"03.grouped-data.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","frontmatter":{"title":"Group Rows by Region and Category","description":"Organize a large inventory table into nested groups while preserving virtualization and sticky headers.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":1028,"sizeBytes":4285},{"name":"04.sticky-columns.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns Example","description":"Pin identity and status columns so users can scan wide datasets without losing the row context.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1048,"sizeBytes":4554},{"name":"06.column-reorder.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","frontmatter":{"title":"Column Reorder Example","description":"Reorder columns with the shadcn drag handle and drop zone components.","sidebar":{"label":"Column Reorder"}},"isInternal":false,"tokens":649,"sizeBytes":2645},{"name":"07.local-filter-sort-group.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","frontmatter":{"title":"Local Filter, Sort, Group","description":"Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.","sidebar":{"label":"Local Filter, Sort, Group"}},"isInternal":false,"tokens":1862,"sizeBytes":7409},{"name":"08.remote-offset.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","frontmatter":{"title":"Server-side Paging","description":"Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.","sidebar":{"label":"Paging"}},"isInternal":false,"tokens":1030,"sizeBytes":4524},{"name":"09.remote-append.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","frontmatter":{"title":"Endless Scrolling","description":"Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.","sidebar":{"label":"Endless Scrolling"}},"isInternal":false,"tokens":835,"sizeBytes":3582},{"name":"10.table-instance-dashboard.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","frontmatter":{"title":"External Toolbar","description":"Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.","sidebar":{"label":"External Toolbar"}},"isInternal":false,"tokens":1181,"sizeBytes":4946},{"name":"11.scrollbar-overlay.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","frontmatter":{"title":"Keep Sticky Columns Clear of Overlay Scrollbars","description":"Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.","sidebar":{"label":"Overlay Scrollbars"}},"isInternal":false,"tokens":741,"sizeBytes":3254},{"name":"12.custom-scroll-container.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","frontmatter":{"title":"Embed a Table in a Shared Scroll Area","description":"Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.","sidebar":{"label":"Shared Scroll Area"}},"isInternal":false,"tokens":812,"sizeBytes":3558},{"name":"13.column-resizing.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","frontmatter":{"title":"Column Resizing Example","description":"Add resize handles and external width controls to a table.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":942,"sizeBytes":4093},{"name":"14.column-visibility.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","frontmatter":{"title":"Column Visibility Example","description":"Build a toolbar that hides and restores columns at runtime.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":937,"sizeBytes":4095},{"name":"15.state-persistence.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","frontmatter":{"title":"State Persistence Example","description":"Persist a remote filter action and column widths for a remote table.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1172,"sizeBytes":5252},{"name":"16.remote-column-sorting.md","path":"packages/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","frontmatter":{"title":"Remote Column Sorting","description":"Use shadcn header buttons to send sort params to a remote data model.","sidebar":{"label":"Remote Sorting"}},"isInternal":false,"tokens":1429,"sizeBytes":6164},{"name":"01.testing.md","path":"packages/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","frontmatter":{"title":"Testing","description":"Make table behavior deterministic in tests by providing explicit test dimensions.","sidebar":{"label":"Testing"}},"isInternal":false,"tokens":631,"sizeBytes":2858},{"name":"02.performance.md","path":"packages/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","frontmatter":{"title":"Performance","description":"Keep render work predictable with stable keys, deliberate overscan, and bounded viewport rendering.","sidebar":{"label":"Performance"}},"isInternal":false,"tokens":734,"sizeBytes":3669},{"name":"03.troubleshooting.md","path":"packages/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting","description":"Diagnose the most common integration issues around layout, imports, and scroll configuration.","sidebar":{"label":"Troubleshooting"}},"isInternal":false,"tokens":788,"sizeBytes":4194},{"name":"04.migrating-from-table-virtuoso.md","path":"packages/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","frontmatter":{"title":"Migrating From TableVirtuoso","description":"Map common TableVirtuoso patterns to the data-table package.","sidebar":{"label":"Migrating From TableVirtuoso"}},"isInternal":false,"tokens":890,"sizeBytes":3935},{"name":"05.debug-instrumentation.md","path":"packages/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","frontmatter":{"title":"Inspecting Row Re-renders","description":"Enable unstable row render events while investigating rendering behavior.","sidebar":{"label":"Row Renders"}},"isInternal":false,"tokens":1203,"sizeBytes":5047},{"name":"SKILL.md","path":"packages/virtuoso-skills/skills/data-table/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/SKILL.md","title":"data-table","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","frontmatter":{"name":"data-table","description":"Build virtualized data tables with @virtuoso.dev/data-table. Use this skill when (1) building a data grid with sorting, filtering, or grouped rows, (2) installing the shadcn-styled or headless table, (3) connecting remote/paginated data sources, (4) adding sticky, resizable, reorderable, or hideable columns, (5) persisting table state, (6) controlling a table from outside (scrolling, actions), (7) migrating from TableVirtuoso, or any task involving VirtuosoDataTable, DataTable, DataTableColumn, localModel, remoteModel, or engine refs like scrollToRow$ and dispatchModelAction$."},"isInternal":false,"tokens":4561,"sizeBytes":21050},{"name":"10.smooth-scrolling.md","path":"packages/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","frontmatter":{"id":"virtuoso-message-list-smooth-scrolling","title":"Virtuoso Message Custom Smooth Scrolling","description":"Customize scroll animations with custom easing functions like bounce or elastic for unique chat UX.","sidebar_label":"Custom Smooth Scrolling","sidebar_position":8,"slug":"/virtuoso-message-list/smooth-scrolling"},"isInternal":false,"tokens":1403,"sizeBytes":5608},{"name":"11.testing.md","path":"packages/virtuoso-skills/skills/message-list/references/11.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/11.testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","frontmatter":{"id":"virtuoso-message-list-testing","title":"Virtuoso Message List Testing","description":"Test VirtuosoMessageList with Playwright E2E or React Testing Library using VirtuosoMessageListTestingContext.","sidebar_label":"Testing","sidebar_position":9,"slug":"/virtuoso-message-list/testing"},"isInternal":false,"tokens":478,"sizeBytes":2081},{"name":"15.imperative-data-api.md","path":"packages/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","frontmatter":{"id":"virtuoso-message-list-working-with-data","title":"Virtuoso Message List - Working with Data","description":"Use imperative data API for append, prepend, map, and delete operations with automatic scroll handling.","sidebar_label":"Imperative Data API","sidebar_position":10,"slug":"/virtuoso-message-list/working-with-data"},"isInternal":false,"tokens":3508,"sizeBytes":15613},{"name":"19.resize-observer-errors.md","path":"packages/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","frontmatter":{"id":"virtuoso-message-list-resize-observer-errors","title":"Virtuoso Message List Resize Observer Errors","description":"Handle benign ResizeObserver loop errors in Webpack dev server and Sentry error tracking.","sidebar_label":"Resize Observer Errors","sidebar_position":10,"slug":"/virtuoso-message-list/resize-observer-errors"},"isInternal":false,"tokens":333,"sizeBytes":1609},{"name":"01.intro.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-intro","title":"Virtuoso Message List Tutorial - Intro","description":"Get started building a chat UI with VirtuosoMessageList - set up the project and define message data structures.","sidebar_label":"Intro","sidebar_position":1,"slug":"/virtuoso-message-list/tutorial/intro"},"isInternal":false,"tokens":865,"sizeBytes":3697},{"name":"02.message-list.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-message-list","title":"Virtuoso Message List Tutorial - Message List","description":"Add the VirtuosoMessageList component to your app with custom ItemContent and context for chat message rendering.","sidebar_label":"Message List","sidebar_position":2,"slug":"/virtuoso-message-list/tutorial/message-list"},"isInternal":false,"tokens":2551,"sizeBytes":11018},{"name":"03.loading-older-messages.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-loading-older-messages","title":"Virtuoso Message List Tutorial - Loading Older Messages","description":"Implement infinite scroll to load chat history when users scroll up using onScroll and prepend scroll modifier.","sidebar_label":"Loading Older Messages","sidebar_position":3,"slug":"/virtuoso-message-list/tutorial/loading-older-messages"},"isInternal":false,"tokens":877,"sizeBytes":3862},{"name":"04.scroll-to-bottom-button.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-scroll-to-bottom-button","title":"Virtuoso Message List Tutorial - Scroll to Bottom Button","description":"Add a scroll-to-bottom button using StickyFooter, useVirtuosoLocation and useVirtuosoMethods hooks.","sidebar_label":"Scroll to Bottom Button","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/scroll-to-bottom-button"},"isInternal":false,"tokens":701,"sizeBytes":3040},{"name":"05.receive-messages.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-receive-messages","title":"Virtuoso Message List Tutorial - Receive Messages","description":"Handle incoming messages with auto-scroll-to-bottom modifier and display unread message indicators.","sidebar_label":"Receive Messages","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/receive-messages"},"isInternal":false,"tokens":1097,"sizeBytes":4957},{"name":"06.send-messages.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-send-messages","title":"Virtuoso Message List Tutorial - Send Messages","description":"Implement message sending with optimistic updates and delivery confirmation in VirtuosoMessageList.","sidebar_label":"Send Messages","sidebar_position":6,"slug":"/virtuoso-message-list/tutorial/send-messages"},"isInternal":false,"tokens":627,"sizeBytes":3016},{"name":"07.multiple-channels.md","path":"packages/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-multiple-channels","title":"Virtuoso Message List Tutorial - Multiple Channels","description":"Support multiple chat channels with independent scroll positions and message history in VirtuosoMessageList.","sidebar_label":"Multiple Channels","sidebar_position":7,"slug":"/virtuoso-message-list/tutorial/multiple-channels"},"isInternal":false,"tokens":625,"sizeBytes":2848},{"name":"20.scroll-modifier.md","path":"packages/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-modifier","title":"Virtuoso Message List Scroll Position Control","description":"Control VirtuosoMessageList scroll behavior with modifiers for prepend, append, item changes, and initial position.","sidebar_label":"Scroll Modifier","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-modifier"},"isInternal":false,"tokens":1563,"sizeBytes":7197},{"name":"21.controlled-mode-recipes.md","path":"packages/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","frontmatter":{"id":"virtuoso-message-list-controlled-mode-recipes","title":"Virtuoso Message List Controlled Mode Recipes","description":"Organize controlled message-list state behind app-owned actions for local sends, incoming messages, streaming updates, and row growth.","sidebar_label":"Controlled Mode Recipes","sidebar_position":11,"slug":"/virtuoso-message-list/controlled-mode-recipes"},"isInternal":false,"tokens":1348,"sizeBytes":6129},{"name":"01.messaging.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","frontmatter":{"id":"virtuoso-message-list-examples-messaging","title":"Virtuoso Message List Examples - Messaging","description":"Complete chat messaging UI example with send, receive, optimistic updates, and multi-channel support.","sidebar_label":"Messaging Interface","sidebar_position":1,"slug":"/virtuoso-message-list/examples/messaging"},"isInternal":false,"tokens":2409,"sizeBytes":10356},{"name":"02.ai-chatbot.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","frontmatter":{"id":"virtuoso-message-list-examples-ai-chatbot","title":"Virtuoso Message List Examples - AI Chatbot","description":"Build AI chatbot interfaces with streaming responses and smooth auto-scroll using VirtuosoMessageList.","sidebar_label":"AI Chatbot","sidebar_position":2,"slug":"/virtuoso-message-list/examples/ai-chatbot"},"isInternal":false,"tokens":1015,"sizeBytes":4504},{"name":"03.gemini.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","frontmatter":{"id":"virtuoso-message-list-examples-gemini","title":"Virtuoso Message List Examples - Gemini-like Chatbot Scroll","description":"Implement Gemini-style chat scroll where questions align to top with streaming responses below.","sidebar_label":"Gemini-like Chatbot Scroll","sidebar_position":3,"slug":"/virtuoso-message-list/examples/gemini"},"isInternal":false,"tokens":1032,"sizeBytes":4584},{"name":"04.reactions.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","frontmatter":{"id":"virtuoso-message-list-examples-reactions","title":"Virtuoso Message List Examples - Reactions","description":"Add emoji reactions to chat messages with proper scroll position handling using items-change modifier.","sidebar_label":"Reactions","sidebar_position":4,"slug":"/virtuoso-message-list/examples/reactions"},"isInternal":false,"tokens":1058,"sizeBytes":4392},{"name":"05.date-separators.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","frontmatter":{"id":"virtuoso-message-list-examples-date-separators","title":"Virtuoso Message List Examples - Date Separators","description":"Display date separators between messages with sticky headers using prevData prop and useCurrentlyRenderedData hook.","sidebar_label":"Date Separators","sidebar_position":5,"slug":"/virtuoso-message-list/examples/date-separators"},"isInternal":false,"tokens":1082,"sizeBytes":4462},{"name":"06.scroll-to-reply.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","frontmatter":{"id":"virtuoso-message-list-examples-scroll-to-reply","title":"Virtuoso Message List Examples - Scroll to Reply","description":"Navigate to quoted messages with smooth scroll and highlight animation using scrollToItem and data.findIndex.","sidebar_label":"Scroll to Reply","sidebar_position":6,"slug":"/virtuoso-message-list/examples/scroll-to-reply"},"isInternal":false,"tokens":1330,"sizeBytes":5524},{"name":"07.grouped-messages.md","path":"packages/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","frontmatter":{"id":"virtuoso-message-list-examples-grouped-messages","title":"Virtuoso Message List Examples - Grouped Messages","description":"Group consecutive messages from the same user with connected bubbles using prevData and nextData props.","sidebar_label":"Grouped Messages","sidebar_position":7,"slug":"/virtuoso-message-list/examples/grouped-messages"},"isInternal":false,"tokens":1071,"sizeBytes":4720},{"name":"4.headers-footers.md","path":"packages/virtuoso-skills/skills/message-list/references/4.headers-footers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/4.headers-footers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","frontmatter":{"id":"virtuoso-message-list-headers-footers","title":"Virtuoso Message List Headers and Footers","description":"Add sticky and scrollable headers and footers to VirtuosoMessageList for loading indicators and overlays.","sidebar_label":"Headers and Footers","sidebar_position":5,"slug":"/virtuoso-message-list/headers-footers"},"isInternal":false,"tokens":1426,"sizeBytes":5588},{"name":"5.context.md","path":"packages/virtuoso-skills/skills/message-list/references/5.context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/5.context.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","frontmatter":{"id":"virtuoso-message-list-context","title":"Virtuoso Message List Context","description":"Pass shared state to ItemContent and custom components without prop drilling using the context prop.","sidebar_label":"Context","sidebar_position":6,"slug":"/virtuoso-message-list/context"},"isInternal":false,"tokens":567,"sizeBytes":2477},{"name":"6.item-keys.md","path":"packages/virtuoso-skills/skills/message-list/references/6.item-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/6.item-keys.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","frontmatter":{"id":"virtuoso-message-list-item-keys","title":"Virtuoso Message List Item Keys","description":"Generate stable React keys for message list items using computeItemKey to prevent unnecessary remounts.","sidebar_label":"Item Keys","sidebar_position":6,"slug":"/virtuoso-message-list/item-keys"},"isInternal":false,"tokens":498,"sizeBytes":2187},{"name":"7.hooks.md","path":"packages/virtuoso-skills/skills/message-list/references/7.hooks.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/7.hooks.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","frontmatter":{"id":"virtuoso-message-list-hooks","title":"Virtuoso Message List Hooks","description":"Access message list methods and scroll state from child components using useVirtuosoMethods and useVirtuosoLocation.","sidebar_label":"Hooks","sidebar_position":7,"slug":"/virtuoso-message-list/hooks"},"isInternal":false,"tokens":496,"sizeBytes":2175},{"name":"8.custom-scrollbar.md","path":"packages/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","frontmatter":{"id":"virtuoso-message-list-custom-scrollbar","title":"Virtuoso Message List Custom Scrollbar","description":"Integrate SimpleBar and other custom scrollbar libraries with VirtuosoMessageList using customScrollParent.","sidebar_label":"Custom Scrollbar Component","sidebar_position":30,"slug":"/virtuoso-message-list/custom-scrollbar"},"isInternal":false,"tokens":345,"sizeBytes":1487},{"name":"80.licensing.md","path":"packages/virtuoso-skills/skills/message-list/references/80.licensing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/80.licensing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","frontmatter":{"id":"virtuoso-message-list-licensing","title":"Virtuoso Message List Licensing","description":"Commercial license options, seat calculation, key setup, and trial terms for Virtuoso Message List.","sidebar_label":"Licensing","sidebar_position":3,"slug":"/virtuoso-message-list/licensing"},"isInternal":false,"tokens":1281,"sizeBytes":6112},{"name":"9.scrolling-to-item.md","path":"packages/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-to-item","title":"Virtuoso Message List Scroll to Item","description":"Programmatically scroll to specific messages using the imperative scrollToItem API with alignment options.","sidebar_label":"Scroll to Item","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-to-item"},"isInternal":false,"tokens":373,"sizeBytes":1553},{"name":"SKILL.md","path":"packages/virtuoso-skills/skills/message-list/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/SKILL.md","title":"message-list","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","frontmatter":{"name":"message-list","description":"Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up, (4) adding scroll-to-bottom buttons or unseen-message indicators, (5) switching between channels/conversations, or (6) any task involving VirtuosoMessageList, VirtuosoMessageListLicense, useVirtuosoMethods, useVirtuosoLocation, scrollModifier, notifyItemsChanged, scrollToBottomIfAtBottom, scrollToBottomAlways, smooth-scroll retargeting, replacement anchors, or data.append/prepend/map/replace. The package is commercial and requires a license key."},"isInternal":false,"tokens":3706,"sizeBytes":16510},{"name":"auto-resizing.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","frontmatter":{"title":"Auto Resizing Virtual List","description":"React Virtuoso automatically handles dynamic item height changes from content resizing, images loading, and container resizing.","sidebar":{"label":"Auto Resizing","order":2}},"isInternal":false,"tokens":341,"sizeBytes":1404},{"name":"basic-usage.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","frontmatter":{"title":"Virtual List With 100,000 Items","description":"Get started with React Virtuoso by rendering a virtual list with 100,000 items of unknown sizes.","sidebar":{"label":"100,000 Items","order":1}},"isInternal":false,"tokens":590,"sizeBytes":2374},{"name":"custom-scroll-container.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","frontmatter":{"title":"Customize Root Element","description":"Customize the React Virtuoso scroller element with custom styling, DOM event handlers, or integrate with custom scrollbar libraries.","position":500,"sidebar":{"label":"Customize Scroller"}},"isInternal":false,"tokens":487,"sizeBytes":2020},{"name":"customize-rendering.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","frontmatter":{"title":"Customize Rendering","description":"Replace React Virtuoso's default DOM elements with custom components for full control over list rendering and styling.","position":501,"sidebar":{"label":"Customize Rendering"}},"isInternal":false,"tokens":604,"sizeBytes":2500},{"name":"endless-scrolling.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","frontmatter":{"title":"Endless Scrolling","description":"The React Virtuoso component makes it trivial to implement infinite scrolling lists in both directions with variably sized items.","sidebar":{"label":"Endless Scrolling","order":100}},"isInternal":false,"tokens":383,"sizeBytes":1584},{"name":"footer.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","frontmatter":{"title":"List with Footer Example","description":"Add a custom footer component to React Virtuoso lists for loading indicators, load more buttons, or end-of-list messages.","sidebar":{"label":"Footer","order":5}},"isInternal":false,"tokens":316,"sizeBytes":1317},{"name":"horizontal-mode.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","frontmatter":{"title":"Horizontal Mode","description":"Render React Virtuoso lists horizontally with the horizontalDirection property for carousels and horizontal scrolling UIs.","sidebar_title":"Horizontal Mode","sidebar":{"order":90}},"isInternal":false,"tokens":321,"sizeBytes":1353},{"name":"initial-index.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","frontmatter":{"title":"Start from a certain item","description":"Use initialTopMostItemIndex to start React Virtuoso lists at a specific item position on mount.","sidebar":{"label":"Initial Index","order":4}},"isInternal":false,"tokens":222,"sizeBytes":903},{"name":"keyboard-navigation.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","frontmatter":{"title":"Keyboard Navigation","description":"Implement keyboard navigation in React Virtuoso lists using the scrollIntoView method with arrow key support.","sidebar":{"label":"Keyboard Navigation","order":121}},"isInternal":false,"tokens":556,"sizeBytes":2337},{"name":"press-to-load-more.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","frontmatter":{"title":"Press to Load More","description":"Add a load more button to React Virtuoso lists for user-triggered pagination instead of automatic infinite scrolling.","sidebar":{"label":"Press to Load More","order":101}},"isInternal":false,"tokens":536,"sizeBytes":2153},{"name":"range-change-callback.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","frontmatter":{"title":"Range Change Callback","description":"Track visible item ranges in React Virtuoso lists using the rangeChanged callback for analytics or lazy loading.","sidebar":{"label":"Range Change Callback","order":101}},"isInternal":false,"tokens":297,"sizeBytes":1189},{"name":"scroll-handling.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","frontmatter":{"title":"Scroll Handling","description":"Optimize React Virtuoso scroll performance by detecting scroll state and replacing heavy content with placeholders.","sidebar":{"label":"Scroll Handling","order":302}},"isInternal":false,"tokens":459,"sizeBytes":1879},{"name":"scroll-seek-placeholders.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","frontmatter":{"title":"Scroll Seek Placeholders","description":"Improve React Virtuoso fast scrolling performance by showing lightweight placeholders during rapid scroll.","sidebar":{"label":"Scroll Seek Placeholders","order":502}},"isInternal":false,"tokens":572,"sizeBytes":2458},{"name":"scroll-to-index.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","frontmatter":{"title":"Scroll to Index","description":"Programmatically scroll to any item in React Virtuoso using scrollToIndex with alignment and smooth scroll options.","sidebar":{"label":"Scroll to Index","order":50}},"isInternal":false,"tokens":650,"sizeBytes":2892},{"name":"tailwind-test.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","frontmatter":{"title":"Tailwind Test"},"isInternal":false,"tokens":592,"sizeBytes":2316},{"name":"top-items.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","frontmatter":{"title":"Top Items List Example","description":"Pin the first N items at the top of React Virtuoso lists with the topItemCount property for sticky headers.","sidebar":{"label":"Top Items"}},"isInternal":false,"tokens":197,"sizeBytes":801},{"name":"window-scrolling.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","frontmatter":{"title":"Window Scrolling","description":"Use the browser window as the scroll container for React Virtuoso lists with the useWindowScroll property.","sidebar":{"label":"Window Scrolling","order":250}},"isInternal":false,"tokens":143,"sizeBytes":543},{"name":"grouped-by-first-letter.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","frontmatter":{"title":"Grouped by First Letter","description":"Build alphabetically grouped lists with sticky headers using GroupedVirtuoso for contact lists and directories.","sidebar":{"label":"Grouped by First Letter","order":2}},"isInternal":false,"tokens":463,"sizeBytes":1946},{"name":"grouped-numbers.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","frontmatter":{"title":"Grouped 10,000 numbers","description":"Display large datasets in groups with sticky headers using React Virtuoso GroupedVirtuoso component.","sidebar":{"label":"Grouped Numbers","order":1}},"isInternal":false,"tokens":244,"sizeBytes":921},{"name":"grouped-with-load-on-demand.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","frontmatter":{"title":"Grouped Load on Demand","description":"Implement infinite scrolling with grouped data in React Virtuoso by dynamically loading and appending groups.","sidebar":{"label":"Load on Demand","order":4}},"isInternal":false,"tokens":763,"sizeBytes":3281},{"name":"scroll-to-group.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","frontmatter":{"title":"Scroll to Group","description":"Build iOS-style contact lists with alphabetical navigation and scroll-to-group functionality in React Virtuoso.","sidebar":{"label":"Scroll to Group","order":5}},"isInternal":false,"tokens":624,"sizeBytes":2836},{"name":"grid-responsive-columns.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","title":"3.virtuoso-grid Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","frontmatter":{"title":"Grid with Responsive Columns","description":"Build responsive virtualized grids with VirtuosoGrid using CSS flexbox or grid for adaptive column layouts.","sidebar":{"label":"Responsive Columns"}},"isInternal":false,"tokens":593,"sizeBytes":2456},{"name":"basic-table.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","frontmatter":{"title":"Table Virtuoso Basic Example","description":"Virtualize large HTML tables with unknown row heights using TableVirtuoso for efficient rendering.","position":1,"sidebar":{"label":"Basic Example"}},"isInternal":false,"tokens":742,"sizeBytes":3007},{"name":"table-fixed-columns.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Columns","description":"Create horizontally scrollable tables with sticky frozen columns using TableVirtuoso and CSS positioning.","sidebar":{"label":"Fixed Columns"}},"isInternal":false,"tokens":431,"sizeBytes":1762},{"name":"table-fixed-headers.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Headers","description":"Add sticky table headers that remain visible while scrolling using TableVirtuoso's fixedHeaderContent prop.","sidebar":{"label":"Fixed Headers"}},"isInternal":false,"tokens":307,"sizeBytes":1281},{"name":"table-grouped.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","frontmatter":{"title":"Table Virtuoso Example with Grouped Rows","description":"Display grouped table data with sticky group headers using GroupedTableVirtuoso for organized data presentation.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":400,"sizeBytes":1621},{"name":"material-ui-endless-scrolling.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","frontmatter":{"title":"MUI List with Endless Scrolling","description":"Use React Virtuoso with Material UI List components for virtualized grouped lists with MUI styling.","sidebar":{"label":"MUI List","order":1}},"isInternal":false,"tokens":255,"sizeBytes":1140},{"name":"mocking-in-tests.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","frontmatter":{"title":"Mocking in tests","description":"Test React Virtuoso components in Jest and Testing Library using VirtuosoMockContext to simulate DOM measurements.","sidebar":{"label":"Mocking in tests"}},"isInternal":false,"tokens":621,"sizeBytes":2380},{"name":"mui-table-virtual-scroll.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","frontmatter":{"title":"Table Virtuoso with MUI Table","description":"Integrate React Virtuoso TableVirtuoso with Material UI Table components for virtualized data grids.","sidebar":{"label":"MUI Table","order":1}},"isInternal":false,"tokens":316,"sizeBytes":1329},{"name":"tanstack-table-integration.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","frontmatter":{"title":"Table Virtuoso integrated with @tanstack/table","description":"Combine TanStack Table's sorting and filtering with TableVirtuoso's virtualization for large datasets.","sidebar":{"label":"@tanstack/table"}},"isInternal":false,"tokens":260,"sizeBytes":1170},{"name":"6.troubleshooting.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting React Virtuoso","description":"Fix common React Virtuoso issues including margin problems, scroll jumping, zero-sized elements, and testing errors.","sidebar":{"label":"Troubleshooting","order":99}},"isInternal":false,"tokens":1291,"sizeBytes":5606},{"name":"SKILL.md","path":"packages/virtuoso-skills/skills/react-virtuoso/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/SKILL.md","title":"react-virtuoso","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","frontmatter":{"name":"react-virtuoso","description":"Build virtualized lists, grids, and tables with react-virtuoso. Use this skill when (1) rendering large or infinite lists, (2) building grouped lists with sticky headers, (3) virtualizing HTML tables, (4) laying out same-sized items in a responsive grid, (5) building feeds or logs that follow new items at the bottom, (6) diagnosing virtualization symptoms such as jumpy scrolling, a list that does not scroll to the bottom, items overlapping, blank items, or \"zero-sized element\" errors, or (7) any task involving Virtuoso, GroupedVirtuoso, VirtuosoGrid, TableVirtuoso, VirtuosoHandle, itemContent, followOutput, or firstItemIndex."},"isInternal":false,"tokens":2976,"sizeBytes":13619},{"name":"01.core-concepts.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","frontmatter":{"title":"Core Concepts","description":"Nodes, engines, and the separation between graph definitions and runtime state."},"isInternal":false,"tokens":1756,"sizeBytes":8056},{"name":"02.engine-and-lifecycle.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","frontmatter":{"title":"Engine and Lifecycle","description":"How engines activate nodes, the three API flavors, engine context, child engines, and disposal."},"isInternal":false,"tokens":1938,"sizeBytes":9457},{"name":"03.transactions.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","frontmatter":{"title":"Transactions and Execution","description":"How publishing works - transactional cycles, topological ordering, and how the engine differs from RxJS."},"isInternal":false,"tokens":1312,"sizeBytes":6271},{"name":"04.operators-and-combinators.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","frontmatter":{"title":"Operators and Combinators","description":"Reference for transforming and connecting nodes."},"isInternal":false,"tokens":1651,"sizeBytes":8877},{"name":"05.building-on-the-engine.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","frontmatter":{"title":"Building on the Engine","description":"Architecture patterns for components and libraries built on the reactive engine, extracted from @virtuoso.dev/data-table."},"isInternal":false,"tokens":1670,"sizeBytes":7803},{"name":"06.diagnostics.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","frontmatter":{"title":"Propagation Diagnostics","description":"Observe structured engine propagation without retaining application state or changing graph behavior."},"isInternal":false,"tokens":2093,"sizeBytes":11216},{"name":"01.queries-and-mutations.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","title":"Query Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","frontmatter":{"title":"Queries and Mutations","description":"Data fetching as nodes - lifecycle, result states, retries, and wiring patterns."},"isInternal":false,"tokens":1171,"sizeBytes":4787},{"name":"01.react-integration.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","title":"React Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","frontmatter":{"title":"React Integration","description":"EngineProvider lifecycle, hooks, and remote engine access."},"isInternal":false,"tokens":2482,"sizeBytes":12587},{"name":"01.routing.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","title":"Router Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","frontmatter":{"title":"Routing","description":"Typed routes as nodes, navigation, layouts, guards, and browser history."},"isInternal":false,"tokens":883,"sizeBytes":3610},{"name":"01.storage-links.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","title":"Storage Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","frontmatter":{"title":"Storage Links","description":"Persisting cells to localStorage, sessionStorage, and cookies."},"isInternal":false,"tokens":1242,"sizeBytes":5714},{"name":"SKILL.md","path":"packages/virtuoso-skills/skills/reactive-engine/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/SKILL.md","title":"reactive-engine","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","frontmatter":{"name":"reactive-engine","description":"Manage application state with the @virtuoso.dev/reactive-engine-* package family. Use this skill when (1) defining reactive state with Cell, Stream, Trigger, or Resource nodes, (2) wiring React components through EngineProvider, useCellValue, useCellValues, or usePublisher, (3) fetching data with Query and Mutation from reactive-engine-query, (4) routing with Route, Layout, and Guard from reactive-engine-router, (5) persisting cells with linkCellToStorage, (6) architecting a component or library on top of the engine, (7) observing or troubleshooting propagation with describeNode, observeDiagnostics, useEngineDiagnostics, or useRemoteEngineDiagnostics, or (8) any task involving Engine, pub, sub, getValue, combine, pipe, link, changeWith, the e namespace, missing or duplicate emissions, unexpected distinct suppression, or the error \"No active engine found\"."},"isInternal":false,"tokens":3612,"sizeBytes":16566},{"name":"tsconfig.json","path":"packages/virtuoso-skills/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/tsconfig.json","title":"Virtuoso-skills Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Ftsconfig.json -->","frontmatter":{"extends":"../../tsconfig.base.json","compilerOptions":{"lib":["ES2024"],"module":"NodeNext","moduleResolution":"NodeNext","types":["node"]},"include":["scripts/**/*.ts"]},"isInternal":false,"tokens":62,"sizeBytes":213},{"name":"01.shadcn.md","path":"plugins/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/1.installation/01.shadcn.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","frontmatter":{"title":"Install With shadcn/ui","description":"Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.","sidebar":{"label":"Shadcn Install"}},"isInternal":false,"tokens":1344,"sizeBytes":5567},{"name":"02.headless.md","path":"plugins/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/1.installation/02.headless.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","frontmatter":{"title":"Install The Headless Package","description":"Use the headless Virtuoso data table package directly when you need full control over styling and composition.","sidebar":{"label":"Headless Install"}},"isInternal":false,"tokens":963,"sizeBytes":3851},{"name":"01.local-data-model.md","path":"plugins/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/2.data-model/01.local-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","frontmatter":{"title":"Local Data Model","description":"Use localModel for in-memory rows, derived views, persistence, and grouped data.","sidebar":{"label":"Local Data Model"}},"isInternal":false,"tokens":3273,"sizeBytes":13929},{"name":"02.remote-data-model.md","path":"plugins/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/2.data-model/02.remote-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","frontmatter":{"title":"Remote Data Model","description":"Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.","sidebar":{"label":"Remote Data Model"}},"isInternal":false,"tokens":2473,"sizeBytes":11459},{"name":"03.row-keys.md","path":"plugins/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/2.data-model/03.row-keys.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","frontmatter":{"title":"Row Keys","description":"Give the table stable row keys so React reconciles rows correctly when the model reorders them.","sidebar":{"label":"Row Keys"}},"isInternal":false,"tokens":676,"sizeBytes":3024},{"name":"index.md","path":"plugins/virtuoso-skills/skills/data-table/references/2.data-model/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/2.data-model/index.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","frontmatter":{"title":"Choosing a Data Model","description":"Give the table a local or remote model before adding table features.","sidebar":{"label":"Choosing a Model"}},"isInternal":false,"tokens":598,"sizeBytes":2633},{"name":"01.defining-columns.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/01.defining-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","frontmatter":{"title":"Defining Columns","description":"Declare the fields, order, defaults, and basic behavior of a data-table column.","sidebar":{"label":"Defining Columns"}},"isInternal":false,"tokens":1113,"sizeBytes":4842},{"name":"02.cell-and-header-renderers.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","frontmatter":{"title":"Formatting Cells and Headers","description":"Use render params to format cells and headers from column and row state.","sidebar":{"label":"Formatting Cells"}},"isInternal":false,"tokens":2511,"sizeBytes":11428},{"name":"03.column-groups.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/03.column-groups.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","frontmatter":{"title":"Column Groups","description":"Organize related columns under shared headers.","sidebar":{"label":"Column Groups"}},"isInternal":false,"tokens":1609,"sizeBytes":7337},{"name":"04.sticky-columns.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/04.sticky-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns","description":"Pin high-value columns to the left or right side of a wide table.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1083,"sizeBytes":4250},{"name":"05.column-visibility.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/05.column-visibility.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","frontmatter":{"title":"Column Visibility","description":"Hide and restore columns with declarative defaults, reactive controls, and persistence.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":1857,"sizeBytes":8403},{"name":"06.column-resizing.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/06.column-resizing.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","frontmatter":{"title":"Column Resizing","description":"Let users change column widths with the opt-in resize model and shadcn resize handle.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":1888,"sizeBytes":8432},{"name":"07.column-reordering.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/07.column-reordering.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","frontmatter":{"title":"Column Reordering","description":"Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.","sidebar":{"label":"Column Reordering"}},"isInternal":false,"tokens":1822,"sizeBytes":7997},{"name":"08.runtime-columns.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/08.runtime-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","frontmatter":{"title":"Generating Columns at Runtime","description":"Discover and declare table columns when the field list is only known after data arrives.","sidebar":{"label":"Runtime Columns"}},"isInternal":false,"tokens":2071,"sizeBytes":9389},{"name":"09.column-layout.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/09.column-layout.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","frontmatter":{"title":"Column Layout","description":"Set base column widths and choose which columns grow into extra horizontal space.","sidebar":{"label":"Column Layout"}},"isInternal":false,"tokens":988,"sizeBytes":4773},{"name":"index.md","path":"plugins/virtuoso-skills/skills/data-table/references/3.columns/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/3.columns/index.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","frontmatter":{"title":"Overview","description":"Define the visible columns, headers, and cells that turn model rows into a grid.","sidebar":{"order":0,"label":"Overview"}},"isInternal":false,"tokens":1096,"sizeBytes":4897},{"name":"4.grouped-rows.md","path":"plugins/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/4.grouped-rows.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","frontmatter":{"title":"Grouped Rows","description":"Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":2792,"sizeBytes":11637},{"name":"5.state-persistence.md","path":"plugins/virtuoso-skills/skills/data-table/references/5.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/5.state-persistence.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","frontmatter":{"title":"State Persistence","description":"Persist table feature state and model action state with opt-in adapters.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1898,"sizeBytes":8940},{"name":"6.controlling-the-table.md","path":"plugins/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/6.controlling-the-table.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","frontmatter":{"title":"Controlling the Table","description":"Read table state and publish table actions from nearby toolbars, panels, and other application UI.","sidebar":{"label":"Controlling the Table"}},"isInternal":false,"tokens":2775,"sizeBytes":12134},{"name":"01.styling.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/01.styling.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","frontmatter":{"title":"Styling Cells, Headers, and Rows","description":"Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.","sidebar":{"label":"Styling"}},"isInternal":false,"tokens":1218,"sizeBytes":5629},{"name":"02.replacing-internals.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/02.replacing-internals.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","frontmatter":{"title":"Replacing Internals","description":"Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.","sidebar":{"label":"Replacing Internals"}},"isInternal":false,"tokens":1043,"sizeBytes":4738},{"name":"03.empty-and-loading-states.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/03.empty-and-loading-states.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","frontmatter":{"title":"Empty, Loading, and Error States","description":"Customize empty placeholders and remote loading states without changing data model.","sidebar":{"label":"Empty and Loading States"}},"isInternal":false,"tokens":815,"sizeBytes":3693},{"name":"04.scroll-containers.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/04.scroll-containers.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","frontmatter":{"title":"Scroll Containers","description":"Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.","sidebar":{"label":"Scroll Containers"}},"isInternal":false,"tokens":1059,"sizeBytes":4654},{"name":"05.ambient-context.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/05.ambient-context.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","frontmatter":{"title":"Ambient Context","description":"A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.","sidebar":{"label":"Ambient Context"}},"isInternal":false,"tokens":860,"sizeBytes":4059},{"name":"06.header-slots.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/06.header-slots.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","frontmatter":{"title":"Header Slots","description":"Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.","sidebar":{"label":"Header Slots"}},"isInternal":false,"tokens":1389,"sizeBytes":6260},{"name":"07.shadcn-wrapper.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/07.shadcn-wrapper.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","frontmatter":{"title":"Inside the shadcn Wrapper","description":"Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.","sidebar":{"label":"The shadcn Wrapper"}},"isInternal":false,"tokens":1570,"sizeBytes":7058},{"name":"08.card-mode.md","path":"plugins/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/7.customization/08.card-mode.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","frontmatter":{"title":"Card Mode","description":"Switch the same data model between table rows and an automatically measured grid of equal-sized cards.","sidebar":{"label":"Card Mode"}},"isInternal":false,"tokens":1607,"sizeBytes":7492},{"name":"01.basic-table.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/01.basic-table.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","frontmatter":{"title":"Basic Table Example","description":"Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.","sidebar":{"label":"Basic Table"}},"isInternal":false,"tokens":699,"sizeBytes":2983},{"name":"03.grouped-data.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/03.grouped-data.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","frontmatter":{"title":"Group Rows by Region and Category","description":"Organize a large inventory table into nested groups while preserving virtualization and sticky headers.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":1028,"sizeBytes":4285},{"name":"04.sticky-columns.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/04.sticky-columns.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns Example","description":"Pin identity and status columns so users can scan wide datasets without losing the row context.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1048,"sizeBytes":4554},{"name":"06.column-reorder.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/06.column-reorder.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","frontmatter":{"title":"Column Reorder Example","description":"Reorder columns with the shadcn drag handle and drop zone components.","sidebar":{"label":"Column Reorder"}},"isInternal":false,"tokens":649,"sizeBytes":2645},{"name":"07.local-filter-sort-group.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/07.local-filter-sort-group.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","frontmatter":{"title":"Local Filter, Sort, Group","description":"Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.","sidebar":{"label":"Local Filter, Sort, Group"}},"isInternal":false,"tokens":1862,"sizeBytes":7409},{"name":"08.remote-offset.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/08.remote-offset.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","frontmatter":{"title":"Server-side Paging","description":"Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.","sidebar":{"label":"Paging"}},"isInternal":false,"tokens":1030,"sizeBytes":4524},{"name":"09.remote-append.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/09.remote-append.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","frontmatter":{"title":"Endless Scrolling","description":"Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.","sidebar":{"label":"Endless Scrolling"}},"isInternal":false,"tokens":835,"sizeBytes":3582},{"name":"10.table-instance-dashboard.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/10.table-instance-dashboard.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","frontmatter":{"title":"External Toolbar","description":"Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.","sidebar":{"label":"External Toolbar"}},"isInternal":false,"tokens":1181,"sizeBytes":4946},{"name":"11.scrollbar-overlay.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/11.scrollbar-overlay.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","frontmatter":{"title":"Keep Sticky Columns Clear of Overlay Scrollbars","description":"Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.","sidebar":{"label":"Overlay Scrollbars"}},"isInternal":false,"tokens":741,"sizeBytes":3254},{"name":"12.custom-scroll-container.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/12.custom-scroll-container.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","frontmatter":{"title":"Embed a Table in a Shared Scroll Area","description":"Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.","sidebar":{"label":"Shared Scroll Area"}},"isInternal":false,"tokens":812,"sizeBytes":3558},{"name":"13.column-resizing.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/13.column-resizing.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","frontmatter":{"title":"Column Resizing Example","description":"Add resize handles and external width controls to a table.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":942,"sizeBytes":4093},{"name":"14.column-visibility.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/14.column-visibility.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","frontmatter":{"title":"Column Visibility Example","description":"Build a toolbar that hides and restores columns at runtime.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":937,"sizeBytes":4095},{"name":"15.state-persistence.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/15.state-persistence.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","frontmatter":{"title":"State Persistence Example","description":"Persist a remote filter action and column widths for a remote table.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1172,"sizeBytes":5252},{"name":"16.remote-column-sorting.md","path":"plugins/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/8.examples/16.remote-column-sorting.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","frontmatter":{"title":"Remote Column Sorting","description":"Use shadcn header buttons to send sort params to a remote data model.","sidebar":{"label":"Remote Sorting"}},"isInternal":false,"tokens":1429,"sizeBytes":6164},{"name":"01.testing.md","path":"plugins/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/9.guides/01.testing.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","frontmatter":{"title":"Testing","description":"Make table behavior deterministic in tests by providing explicit test dimensions.","sidebar":{"label":"Testing"}},"isInternal":false,"tokens":631,"sizeBytes":2858},{"name":"02.performance.md","path":"plugins/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/9.guides/02.performance.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","frontmatter":{"title":"Performance","description":"Keep render work predictable with stable keys, deliberate overscan, and bounded viewport rendering.","sidebar":{"label":"Performance"}},"isInternal":false,"tokens":734,"sizeBytes":3669},{"name":"03.troubleshooting.md","path":"plugins/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/9.guides/03.troubleshooting.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting","description":"Diagnose the most common integration issues around layout, imports, and scroll configuration.","sidebar":{"label":"Troubleshooting"}},"isInternal":false,"tokens":788,"sizeBytes":4194},{"name":"04.migrating-from-table-virtuoso.md","path":"plugins/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","frontmatter":{"title":"Migrating From TableVirtuoso","description":"Map common TableVirtuoso patterns to the data-table package.","sidebar":{"label":"Migrating From TableVirtuoso"}},"isInternal":false,"tokens":890,"sizeBytes":3935},{"name":"05.debug-instrumentation.md","path":"plugins/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/9.guides/05.debug-instrumentation.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","frontmatter":{"title":"Inspecting Row Re-renders","description":"Enable unstable row render events while investigating rendering behavior.","sidebar":{"label":"Row Renders"}},"isInternal":false,"tokens":1203,"sizeBytes":5047},{"name":"SKILL.md","path":"plugins/virtuoso-skills/skills/data-table/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/SKILL.md","title":"data-table","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2FSKILL.md -->","frontmatter":{"name":"data-table","description":"Build virtualized data tables with @virtuoso.dev/data-table. Use this skill when (1) building a data grid with sorting, filtering, or grouped rows, (2) installing the shadcn-styled or headless table, (3) connecting remote/paginated data sources, (4) adding sticky, resizable, reorderable, or hideable columns, (5) persisting table state, (6) controlling a table from outside (scrolling, actions), (7) migrating from TableVirtuoso, or any task involving VirtuosoDataTable, DataTable, DataTableColumn, localModel, remoteModel, or engine refs like scrollToRow$ and dispatchModelAction$."},"isInternal":false,"tokens":4561,"sizeBytes":21050},{"name":"10.smooth-scrolling.md","path":"plugins/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/10.smooth-scrolling.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","frontmatter":{"id":"virtuoso-message-list-smooth-scrolling","title":"Virtuoso Message Custom Smooth Scrolling","description":"Customize scroll animations with custom easing functions like bounce or elastic for unique chat UX.","sidebar_label":"Custom Smooth Scrolling","sidebar_position":8,"slug":"/virtuoso-message-list/smooth-scrolling"},"isInternal":false,"tokens":1403,"sizeBytes":5608},{"name":"11.testing.md","path":"plugins/virtuoso-skills/skills/message-list/references/11.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/11.testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F11.testing.md -->","frontmatter":{"id":"virtuoso-message-list-testing","title":"Virtuoso Message List Testing","description":"Test VirtuosoMessageList with Playwright E2E or React Testing Library using VirtuosoMessageListTestingContext.","sidebar_label":"Testing","sidebar_position":9,"slug":"/virtuoso-message-list/testing"},"isInternal":false,"tokens":478,"sizeBytes":2081},{"name":"15.imperative-data-api.md","path":"plugins/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/15.imperative-data-api.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","frontmatter":{"id":"virtuoso-message-list-working-with-data","title":"Virtuoso Message List - Working with Data","description":"Use imperative data API for append, prepend, map, and delete operations with automatic scroll handling.","sidebar_label":"Imperative Data API","sidebar_position":10,"slug":"/virtuoso-message-list/working-with-data"},"isInternal":false,"tokens":3508,"sizeBytes":15613},{"name":"19.resize-observer-errors.md","path":"plugins/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/19.resize-observer-errors.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","frontmatter":{"id":"virtuoso-message-list-resize-observer-errors","title":"Virtuoso Message List Resize Observer Errors","description":"Handle benign ResizeObserver loop errors in Webpack dev server and Sentry error tracking.","sidebar_label":"Resize Observer Errors","sidebar_position":10,"slug":"/virtuoso-message-list/resize-observer-errors"},"isInternal":false,"tokens":333,"sizeBytes":1609},{"name":"01.intro.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/01.intro.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-intro","title":"Virtuoso Message List Tutorial - Intro","description":"Get started building a chat UI with VirtuosoMessageList - set up the project and define message data structures.","sidebar_label":"Intro","sidebar_position":1,"slug":"/virtuoso-message-list/tutorial/intro"},"isInternal":false,"tokens":865,"sizeBytes":3697},{"name":"02.message-list.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/02.message-list.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-message-list","title":"Virtuoso Message List Tutorial - Message List","description":"Add the VirtuosoMessageList component to your app with custom ItemContent and context for chat message rendering.","sidebar_label":"Message List","sidebar_position":2,"slug":"/virtuoso-message-list/tutorial/message-list"},"isInternal":false,"tokens":2551,"sizeBytes":11018},{"name":"03.loading-older-messages.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/03.loading-older-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-loading-older-messages","title":"Virtuoso Message List Tutorial - Loading Older Messages","description":"Implement infinite scroll to load chat history when users scroll up using onScroll and prepend scroll modifier.","sidebar_label":"Loading Older Messages","sidebar_position":3,"slug":"/virtuoso-message-list/tutorial/loading-older-messages"},"isInternal":false,"tokens":877,"sizeBytes":3862},{"name":"04.scroll-to-bottom-button.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-scroll-to-bottom-button","title":"Virtuoso Message List Tutorial - Scroll to Bottom Button","description":"Add a scroll-to-bottom button using StickyFooter, useVirtuosoLocation and useVirtuosoMethods hooks.","sidebar_label":"Scroll to Bottom Button","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/scroll-to-bottom-button"},"isInternal":false,"tokens":701,"sizeBytes":3040},{"name":"05.receive-messages.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/05.receive-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-receive-messages","title":"Virtuoso Message List Tutorial - Receive Messages","description":"Handle incoming messages with auto-scroll-to-bottom modifier and display unread message indicators.","sidebar_label":"Receive Messages","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/receive-messages"},"isInternal":false,"tokens":1097,"sizeBytes":4957},{"name":"06.send-messages.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/06.send-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-send-messages","title":"Virtuoso Message List Tutorial - Send Messages","description":"Implement message sending with optimistic updates and delivery confirmation in VirtuosoMessageList.","sidebar_label":"Send Messages","sidebar_position":6,"slug":"/virtuoso-message-list/tutorial/send-messages"},"isInternal":false,"tokens":627,"sizeBytes":3016},{"name":"07.multiple-channels.md","path":"plugins/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/2.tutorial/07.multiple-channels.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-multiple-channels","title":"Virtuoso Message List Tutorial - Multiple Channels","description":"Support multiple chat channels with independent scroll positions and message history in VirtuosoMessageList.","sidebar_label":"Multiple Channels","sidebar_position":7,"slug":"/virtuoso-message-list/tutorial/multiple-channels"},"isInternal":false,"tokens":625,"sizeBytes":2848},{"name":"20.scroll-modifier.md","path":"plugins/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/20.scroll-modifier.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-modifier","title":"Virtuoso Message List Scroll Position Control","description":"Control VirtuosoMessageList scroll behavior with modifiers for prepend, append, item changes, and initial position.","sidebar_label":"Scroll Modifier","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-modifier"},"isInternal":false,"tokens":1563,"sizeBytes":7197},{"name":"21.controlled-mode-recipes.md","path":"plugins/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/21.controlled-mode-recipes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","frontmatter":{"id":"virtuoso-message-list-controlled-mode-recipes","title":"Virtuoso Message List Controlled Mode Recipes","description":"Organize controlled message-list state behind app-owned actions for local sends, incoming messages, streaming updates, and row growth.","sidebar_label":"Controlled Mode Recipes","sidebar_position":11,"slug":"/virtuoso-message-list/controlled-mode-recipes"},"isInternal":false,"tokens":1348,"sizeBytes":6129},{"name":"01.messaging.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/01.messaging.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","frontmatter":{"id":"virtuoso-message-list-examples-messaging","title":"Virtuoso Message List Examples - Messaging","description":"Complete chat messaging UI example with send, receive, optimistic updates, and multi-channel support.","sidebar_label":"Messaging Interface","sidebar_position":1,"slug":"/virtuoso-message-list/examples/messaging"},"isInternal":false,"tokens":2409,"sizeBytes":10356},{"name":"02.ai-chatbot.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/02.ai-chatbot.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","frontmatter":{"id":"virtuoso-message-list-examples-ai-chatbot","title":"Virtuoso Message List Examples - AI Chatbot","description":"Build AI chatbot interfaces with streaming responses and smooth auto-scroll using VirtuosoMessageList.","sidebar_label":"AI Chatbot","sidebar_position":2,"slug":"/virtuoso-message-list/examples/ai-chatbot"},"isInternal":false,"tokens":1015,"sizeBytes":4504},{"name":"03.gemini.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/03.gemini.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","frontmatter":{"id":"virtuoso-message-list-examples-gemini","title":"Virtuoso Message List Examples - Gemini-like Chatbot Scroll","description":"Implement Gemini-style chat scroll where questions align to top with streaming responses below.","sidebar_label":"Gemini-like Chatbot Scroll","sidebar_position":3,"slug":"/virtuoso-message-list/examples/gemini"},"isInternal":false,"tokens":1032,"sizeBytes":4584},{"name":"04.reactions.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/04.reactions.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","frontmatter":{"id":"virtuoso-message-list-examples-reactions","title":"Virtuoso Message List Examples - Reactions","description":"Add emoji reactions to chat messages with proper scroll position handling using items-change modifier.","sidebar_label":"Reactions","sidebar_position":4,"slug":"/virtuoso-message-list/examples/reactions"},"isInternal":false,"tokens":1058,"sizeBytes":4392},{"name":"05.date-separators.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/05.date-separators.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","frontmatter":{"id":"virtuoso-message-list-examples-date-separators","title":"Virtuoso Message List Examples - Date Separators","description":"Display date separators between messages with sticky headers using prevData prop and useCurrentlyRenderedData hook.","sidebar_label":"Date Separators","sidebar_position":5,"slug":"/virtuoso-message-list/examples/date-separators"},"isInternal":false,"tokens":1082,"sizeBytes":4462},{"name":"06.scroll-to-reply.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/06.scroll-to-reply.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","frontmatter":{"id":"virtuoso-message-list-examples-scroll-to-reply","title":"Virtuoso Message List Examples - Scroll to Reply","description":"Navigate to quoted messages with smooth scroll and highlight animation using scrollToItem and data.findIndex.","sidebar_label":"Scroll to Reply","sidebar_position":6,"slug":"/virtuoso-message-list/examples/scroll-to-reply"},"isInternal":false,"tokens":1330,"sizeBytes":5524},{"name":"07.grouped-messages.md","path":"plugins/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/3.examples/07.grouped-messages.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","frontmatter":{"id":"virtuoso-message-list-examples-grouped-messages","title":"Virtuoso Message List Examples - Grouped Messages","description":"Group consecutive messages from the same user with connected bubbles using prevData and nextData props.","sidebar_label":"Grouped Messages","sidebar_position":7,"slug":"/virtuoso-message-list/examples/grouped-messages"},"isInternal":false,"tokens":1071,"sizeBytes":4720},{"name":"4.headers-footers.md","path":"plugins/virtuoso-skills/skills/message-list/references/4.headers-footers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/4.headers-footers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","frontmatter":{"id":"virtuoso-message-list-headers-footers","title":"Virtuoso Message List Headers and Footers","description":"Add sticky and scrollable headers and footers to VirtuosoMessageList for loading indicators and overlays.","sidebar_label":"Headers and Footers","sidebar_position":5,"slug":"/virtuoso-message-list/headers-footers"},"isInternal":false,"tokens":1426,"sizeBytes":5588},{"name":"5.context.md","path":"plugins/virtuoso-skills/skills/message-list/references/5.context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/5.context.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F5.context.md -->","frontmatter":{"id":"virtuoso-message-list-context","title":"Virtuoso Message List Context","description":"Pass shared state to ItemContent and custom components without prop drilling using the context prop.","sidebar_label":"Context","sidebar_position":6,"slug":"/virtuoso-message-list/context"},"isInternal":false,"tokens":567,"sizeBytes":2477},{"name":"6.item-keys.md","path":"plugins/virtuoso-skills/skills/message-list/references/6.item-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/6.item-keys.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","frontmatter":{"id":"virtuoso-message-list-item-keys","title":"Virtuoso Message List Item Keys","description":"Generate stable React keys for message list items using computeItemKey to prevent unnecessary remounts.","sidebar_label":"Item Keys","sidebar_position":6,"slug":"/virtuoso-message-list/item-keys"},"isInternal":false,"tokens":498,"sizeBytes":2187},{"name":"7.hooks.md","path":"plugins/virtuoso-skills/skills/message-list/references/7.hooks.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/7.hooks.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F7.hooks.md -->","frontmatter":{"id":"virtuoso-message-list-hooks","title":"Virtuoso Message List Hooks","description":"Access message list methods and scroll state from child components using useVirtuosoMethods and useVirtuosoLocation.","sidebar_label":"Hooks","sidebar_position":7,"slug":"/virtuoso-message-list/hooks"},"isInternal":false,"tokens":496,"sizeBytes":2175},{"name":"8.custom-scrollbar.md","path":"plugins/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/8.custom-scrollbar.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","frontmatter":{"id":"virtuoso-message-list-custom-scrollbar","title":"Virtuoso Message List Custom Scrollbar","description":"Integrate SimpleBar and other custom scrollbar libraries with VirtuosoMessageList using customScrollParent.","sidebar_label":"Custom Scrollbar Component","sidebar_position":30,"slug":"/virtuoso-message-list/custom-scrollbar"},"isInternal":false,"tokens":345,"sizeBytes":1487},{"name":"80.licensing.md","path":"plugins/virtuoso-skills/skills/message-list/references/80.licensing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/80.licensing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F80.licensing.md -->","frontmatter":{"id":"virtuoso-message-list-licensing","title":"Virtuoso Message List Licensing","description":"Commercial license options, seat calculation, key setup, and trial terms for Virtuoso Message List.","sidebar_label":"Licensing","sidebar_position":3,"slug":"/virtuoso-message-list/licensing"},"isInternal":false,"tokens":1281,"sizeBytes":6112},{"name":"9.scrolling-to-item.md","path":"plugins/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/9.scrolling-to-item.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-to-item","title":"Virtuoso Message List Scroll to Item","description":"Programmatically scroll to specific messages using the imperative scrollToItem API with alignment options.","sidebar_label":"Scroll to Item","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-to-item"},"isInternal":false,"tokens":373,"sizeBytes":1553},{"name":"SKILL.md","path":"plugins/virtuoso-skills/skills/message-list/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/SKILL.md","title":"message-list","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2FSKILL.md -->","frontmatter":{"name":"message-list","description":"Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up, (4) adding scroll-to-bottom buttons or unseen-message indicators, (5) switching between channels/conversations, or (6) any task involving VirtuosoMessageList, VirtuosoMessageListLicense, useVirtuosoMethods, useVirtuosoLocation, scrollModifier, notifyItemsChanged, scrollToBottomIfAtBottom, scrollToBottomAlways, smooth-scroll retargeting, replacement anchors, or data.append/prepend/map/replace. The package is commercial and requires a license key."},"isInternal":false,"tokens":3706,"sizeBytes":16510},{"name":"auto-resizing.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","frontmatter":{"title":"Auto Resizing Virtual List","description":"React Virtuoso automatically handles dynamic item height changes from content resizing, images loading, and container resizing.","sidebar":{"label":"Auto Resizing","order":2}},"isInternal":false,"tokens":341,"sizeBytes":1404},{"name":"basic-usage.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","frontmatter":{"title":"Virtual List With 100,000 Items","description":"Get started with React Virtuoso by rendering a virtual list with 100,000 items of unknown sizes.","sidebar":{"label":"100,000 Items","order":1}},"isInternal":false,"tokens":590,"sizeBytes":2374},{"name":"custom-scroll-container.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","frontmatter":{"title":"Customize Root Element","description":"Customize the React Virtuoso scroller element with custom styling, DOM event handlers, or integrate with custom scrollbar libraries.","position":500,"sidebar":{"label":"Customize Scroller"}},"isInternal":false,"tokens":487,"sizeBytes":2020},{"name":"customize-rendering.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","frontmatter":{"title":"Customize Rendering","description":"Replace React Virtuoso's default DOM elements with custom components for full control over list rendering and styling.","position":501,"sidebar":{"label":"Customize Rendering"}},"isInternal":false,"tokens":604,"sizeBytes":2500},{"name":"endless-scrolling.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","frontmatter":{"title":"Endless Scrolling","description":"The React Virtuoso component makes it trivial to implement infinite scrolling lists in both directions with variably sized items.","sidebar":{"label":"Endless Scrolling","order":100}},"isInternal":false,"tokens":383,"sizeBytes":1584},{"name":"footer.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/footer.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","frontmatter":{"title":"List with Footer Example","description":"Add a custom footer component to React Virtuoso lists for loading indicators, load more buttons, or end-of-list messages.","sidebar":{"label":"Footer","order":5}},"isInternal":false,"tokens":316,"sizeBytes":1317},{"name":"horizontal-mode.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","frontmatter":{"title":"Horizontal Mode","description":"Render React Virtuoso lists horizontally with the horizontalDirection property for carousels and horizontal scrolling UIs.","sidebar_title":"Horizontal Mode","sidebar":{"order":90}},"isInternal":false,"tokens":321,"sizeBytes":1353},{"name":"initial-index.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/initial-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","frontmatter":{"title":"Start from a certain item","description":"Use initialTopMostItemIndex to start React Virtuoso lists at a specific item position on mount.","sidebar":{"label":"Initial Index","order":4}},"isInternal":false,"tokens":222,"sizeBytes":903},{"name":"keyboard-navigation.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","frontmatter":{"title":"Keyboard Navigation","description":"Implement keyboard navigation in React Virtuoso lists using the scrollIntoView method with arrow key support.","sidebar":{"label":"Keyboard Navigation","order":121}},"isInternal":false,"tokens":556,"sizeBytes":2337},{"name":"press-to-load-more.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","frontmatter":{"title":"Press to Load More","description":"Add a load more button to React Virtuoso lists for user-triggered pagination instead of automatic infinite scrolling.","sidebar":{"label":"Press to Load More","order":101}},"isInternal":false,"tokens":536,"sizeBytes":2153},{"name":"range-change-callback.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","frontmatter":{"title":"Range Change Callback","description":"Track visible item ranges in React Virtuoso lists using the rangeChanged callback for analytics or lazy loading.","sidebar":{"label":"Range Change Callback","order":101}},"isInternal":false,"tokens":297,"sizeBytes":1189},{"name":"scroll-handling.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","frontmatter":{"title":"Scroll Handling","description":"Optimize React Virtuoso scroll performance by detecting scroll state and replacing heavy content with placeholders.","sidebar":{"label":"Scroll Handling","order":302}},"isInternal":false,"tokens":459,"sizeBytes":1879},{"name":"scroll-seek-placeholders.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","frontmatter":{"title":"Scroll Seek Placeholders","description":"Improve React Virtuoso fast scrolling performance by showing lightweight placeholders during rapid scroll.","sidebar":{"label":"Scroll Seek Placeholders","order":502}},"isInternal":false,"tokens":572,"sizeBytes":2458},{"name":"scroll-to-index.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","frontmatter":{"title":"Scroll to Index","description":"Programmatically scroll to any item in React Virtuoso using scrollToIndex with alignment and smooth scroll options.","sidebar":{"label":"Scroll to Index","order":50}},"isInternal":false,"tokens":650,"sizeBytes":2892},{"name":"tailwind-test.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","frontmatter":{"title":"Tailwind Test"},"isInternal":false,"tokens":592,"sizeBytes":2316},{"name":"top-items.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/top-items.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","frontmatter":{"title":"Top Items List Example","description":"Pin the first N items at the top of React Virtuoso lists with the topItemCount property for sticky headers.","sidebar":{"label":"Top Items"}},"isInternal":false,"tokens":197,"sizeBytes":801},{"name":"window-scrolling.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","frontmatter":{"title":"Window Scrolling","description":"Use the browser window as the scroll container for React Virtuoso lists with the useWindowScroll property.","sidebar":{"label":"Window Scrolling","order":250}},"isInternal":false,"tokens":143,"sizeBytes":543},{"name":"grouped-by-first-letter.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","frontmatter":{"title":"Grouped by First Letter","description":"Build alphabetically grouped lists with sticky headers using GroupedVirtuoso for contact lists and directories.","sidebar":{"label":"Grouped by First Letter","order":2}},"isInternal":false,"tokens":463,"sizeBytes":1946},{"name":"grouped-numbers.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","frontmatter":{"title":"Grouped 10,000 numbers","description":"Display large datasets in groups with sticky headers using React Virtuoso GroupedVirtuoso component.","sidebar":{"label":"Grouped Numbers","order":1}},"isInternal":false,"tokens":244,"sizeBytes":921},{"name":"grouped-with-load-on-demand.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","frontmatter":{"title":"Grouped Load on Demand","description":"Implement infinite scrolling with grouped data in React Virtuoso by dynamically loading and appending groups.","sidebar":{"label":"Load on Demand","order":4}},"isInternal":false,"tokens":763,"sizeBytes":3281},{"name":"scroll-to-group.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","frontmatter":{"title":"Scroll to Group","description":"Build iOS-style contact lists with alphabetical navigation and scroll-to-group functionality in React Virtuoso.","sidebar":{"label":"Scroll to Group","order":5}},"isInternal":false,"tokens":624,"sizeBytes":2836},{"name":"grid-responsive-columns.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","title":"3.virtuoso-grid Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","frontmatter":{"title":"Grid with Responsive Columns","description":"Build responsive virtualized grids with VirtuosoGrid using CSS flexbox or grid for adaptive column layouts.","sidebar":{"label":"Responsive Columns"}},"isInternal":false,"tokens":593,"sizeBytes":2456},{"name":"basic-table.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","frontmatter":{"title":"Table Virtuoso Basic Example","description":"Virtualize large HTML tables with unknown row heights using TableVirtuoso for efficient rendering.","position":1,"sidebar":{"label":"Basic Example"}},"isInternal":false,"tokens":742,"sizeBytes":3007},{"name":"table-fixed-columns.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Columns","description":"Create horizontally scrollable tables with sticky frozen columns using TableVirtuoso and CSS positioning.","sidebar":{"label":"Fixed Columns"}},"isInternal":false,"tokens":431,"sizeBytes":1762},{"name":"table-fixed-headers.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Headers","description":"Add sticky table headers that remain visible while scrolling using TableVirtuoso's fixedHeaderContent prop.","sidebar":{"label":"Fixed Headers"}},"isInternal":false,"tokens":307,"sizeBytes":1281},{"name":"table-grouped.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","frontmatter":{"title":"Table Virtuoso Example with Grouped Rows","description":"Display grouped table data with sticky group headers using GroupedTableVirtuoso for organized data presentation.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":400,"sizeBytes":1621},{"name":"material-ui-endless-scrolling.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","frontmatter":{"title":"MUI List with Endless Scrolling","description":"Use React Virtuoso with Material UI List components for virtualized grouped lists with MUI styling.","sidebar":{"label":"MUI List","order":1}},"isInternal":false,"tokens":255,"sizeBytes":1140},{"name":"mocking-in-tests.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","frontmatter":{"title":"Mocking in tests","description":"Test React Virtuoso components in Jest and Testing Library using VirtuosoMockContext to simulate DOM measurements.","sidebar":{"label":"Mocking in tests"}},"isInternal":false,"tokens":621,"sizeBytes":2380},{"name":"mui-table-virtual-scroll.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","frontmatter":{"title":"Table Virtuoso with MUI Table","description":"Integrate React Virtuoso TableVirtuoso with Material UI Table components for virtualized data grids.","sidebar":{"label":"MUI Table","order":1}},"isInternal":false,"tokens":316,"sizeBytes":1329},{"name":"tanstack-table-integration.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","frontmatter":{"title":"Table Virtuoso integrated with @tanstack/table","description":"Combine TanStack Table's sorting and filtering with TableVirtuoso's virtualization for large datasets.","sidebar":{"label":"@tanstack/table"}},"isInternal":false,"tokens":260,"sizeBytes":1170},{"name":"6.troubleshooting.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/6.troubleshooting.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting React Virtuoso","description":"Fix common React Virtuoso issues including margin problems, scroll jumping, zero-sized elements, and testing errors.","sidebar":{"label":"Troubleshooting","order":99}},"isInternal":false,"tokens":1291,"sizeBytes":5606},{"name":"SKILL.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/SKILL.md","title":"react-virtuoso","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2FSKILL.md -->","frontmatter":{"name":"react-virtuoso","description":"Build virtualized lists, grids, and tables with react-virtuoso. Use this skill when (1) rendering large or infinite lists, (2) building grouped lists with sticky headers, (3) virtualizing HTML tables, (4) laying out same-sized items in a responsive grid, (5) building feeds or logs that follow new items at the bottom, (6) diagnosing virtualization symptoms such as jumpy scrolling, a list that does not scroll to the bottom, items overlapping, blank items, or \"zero-sized element\" errors, or (7) any task involving Virtuoso, GroupedVirtuoso, VirtuosoGrid, TableVirtuoso, VirtuosoHandle, itemContent, followOutput, or firstItemIndex."},"isInternal":false,"tokens":2976,"sizeBytes":13619},{"name":"01.core-concepts.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/01.core-concepts.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","frontmatter":{"title":"Core Concepts","description":"Nodes, engines, and the separation between graph definitions and runtime state."},"isInternal":false,"tokens":1756,"sizeBytes":8056},{"name":"02.engine-and-lifecycle.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","frontmatter":{"title":"Engine and Lifecycle","description":"How engines activate nodes, the three API flavors, engine context, child engines, and disposal."},"isInternal":false,"tokens":1938,"sizeBytes":9457},{"name":"03.transactions.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/03.transactions.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","frontmatter":{"title":"Transactions and Execution","description":"How publishing works - transactional cycles, topological ordering, and how the engine differs from RxJS."},"isInternal":false,"tokens":1312,"sizeBytes":6271},{"name":"04.operators-and-combinators.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/04.operators-and-combinators.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","frontmatter":{"title":"Operators and Combinators","description":"Reference for transforming and connecting nodes."},"isInternal":false,"tokens":1651,"sizeBytes":8877},{"name":"05.building-on-the-engine.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/05.building-on-the-engine.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","frontmatter":{"title":"Building on the Engine","description":"Architecture patterns for components and libraries built on the reactive engine, extracted from @virtuoso.dev/data-table."},"isInternal":false,"tokens":1670,"sizeBytes":7803},{"name":"06.diagnostics.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/06.diagnostics.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","frontmatter":{"title":"Propagation Diagnostics","description":"Observe structured engine propagation without retaining application state or changing graph behavior."},"isInternal":false,"tokens":2093,"sizeBytes":11216},{"name":"01.queries-and-mutations.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/query/01.queries-and-mutations.md","title":"Query Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","frontmatter":{"title":"Queries and Mutations","description":"Data fetching as nodes - lifecycle, result states, retries, and wiring patterns."},"isInternal":false,"tokens":1171,"sizeBytes":4787},{"name":"01.react-integration.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/react/01.react-integration.md","title":"React Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","frontmatter":{"title":"React Integration","description":"EngineProvider lifecycle, hooks, and remote engine access."},"isInternal":false,"tokens":2482,"sizeBytes":12587},{"name":"01.routing.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/router/01.routing.md","title":"Router Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","frontmatter":{"title":"Routing","description":"Typed routes as nodes, navigation, layouts, guards, and browser history."},"isInternal":false,"tokens":883,"sizeBytes":3610},{"name":"01.storage-links.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/storage/01.storage-links.md","title":"Storage Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","frontmatter":{"title":"Storage Links","description":"Persisting cells to localStorage, sessionStorage, and cookies."},"isInternal":false,"tokens":1242,"sizeBytes":5714},{"name":"SKILL.md","path":"plugins/virtuoso-skills/skills/reactive-engine/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/SKILL.md","title":"reactive-engine","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2FSKILL.md -->","frontmatter":{"name":"reactive-engine","description":"Manage application state with the @virtuoso.dev/reactive-engine-* package family. Use this skill when (1) defining reactive state with Cell, Stream, Trigger, or Resource nodes, (2) wiring React components through EngineProvider, useCellValue, useCellValues, or usePublisher, (3) fetching data with Query and Mutation from reactive-engine-query, (4) routing with Route, Layout, and Guard from reactive-engine-router, (5) persisting cells with linkCellToStorage, (6) architecting a component or library on top of the engine, (7) observing or troubleshooting propagation with describeNode, observeDiagnostics, useEngineDiagnostics, or useRemoteEngineDiagnostics, or (8) any task involving Engine, pub, sub, getValue, combine, pipe, link, changeWith, the e namespace, missing or duplicate emissions, unexpected distinct suppression, or the error \"No active engine found\"."},"isInternal":false,"tokens":3612,"sizeBytes":16566},{"name":"01.shadcn.md","path":"skills/data-table/references/1.installation/01.shadcn.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/1.installation/01.shadcn.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F01.shadcn.md -->","frontmatter":{"title":"Install With shadcn/ui","description":"Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.","sidebar":{"label":"Shadcn Install"}},"isInternal":false,"tokens":1344,"sizeBytes":5567},{"name":"02.headless.md","path":"skills/data-table/references/1.installation/02.headless.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/1.installation/02.headless.md","title":"1.installation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F1.installation%2F02.headless.md -->","frontmatter":{"title":"Install The Headless Package","description":"Use the headless Virtuoso data table package directly when you need full control over styling and composition.","sidebar":{"label":"Headless Install"}},"isInternal":false,"tokens":963,"sizeBytes":3851},{"name":"01.local-data-model.md","path":"skills/data-table/references/2.data-model/01.local-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/2.data-model/01.local-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F01.local-data-model.md -->","frontmatter":{"title":"Local Data Model","description":"Use localModel for in-memory rows, derived views, persistence, and grouped data.","sidebar":{"label":"Local Data Model"}},"isInternal":false,"tokens":3273,"sizeBytes":13929},{"name":"02.remote-data-model.md","path":"skills/data-table/references/2.data-model/02.remote-data-model.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/2.data-model/02.remote-data-model.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F02.remote-data-model.md -->","frontmatter":{"title":"Remote Data Model","description":"Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.","sidebar":{"label":"Remote Data Model"}},"isInternal":false,"tokens":2473,"sizeBytes":11459},{"name":"03.row-keys.md","path":"skills/data-table/references/2.data-model/03.row-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/2.data-model/03.row-keys.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2F03.row-keys.md -->","frontmatter":{"title":"Row Keys","description":"Give the table stable row keys so React reconciles rows correctly when the model reorders them.","sidebar":{"label":"Row Keys"}},"isInternal":false,"tokens":676,"sizeBytes":3024},{"name":"index.md","path":"skills/data-table/references/2.data-model/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/2.data-model/index.md","title":"2.data-model Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F2.data-model%2Findex.md -->","frontmatter":{"title":"Choosing a Data Model","description":"Give the table a local or remote model before adding table features.","sidebar":{"label":"Choosing a Model"}},"isInternal":false,"tokens":598,"sizeBytes":2633},{"name":"01.defining-columns.md","path":"skills/data-table/references/3.columns/01.defining-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/01.defining-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F01.defining-columns.md -->","frontmatter":{"title":"Defining Columns","description":"Declare the fields, order, defaults, and basic behavior of a data-table column.","sidebar":{"label":"Defining Columns"}},"isInternal":false,"tokens":1113,"sizeBytes":4842},{"name":"02.cell-and-header-renderers.md","path":"skills/data-table/references/3.columns/02.cell-and-header-renderers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/02.cell-and-header-renderers.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F02.cell-and-header-renderers.md -->","frontmatter":{"title":"Formatting Cells and Headers","description":"Use render params to format cells and headers from column and row state.","sidebar":{"label":"Formatting Cells"}},"isInternal":false,"tokens":2511,"sizeBytes":11428},{"name":"03.column-groups.md","path":"skills/data-table/references/3.columns/03.column-groups.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/03.column-groups.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F03.column-groups.md -->","frontmatter":{"title":"Column Groups","description":"Organize related columns under shared headers.","sidebar":{"label":"Column Groups"}},"isInternal":false,"tokens":1609,"sizeBytes":7337},{"name":"04.sticky-columns.md","path":"skills/data-table/references/3.columns/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/04.sticky-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns","description":"Pin high-value columns to the left or right side of a wide table.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1083,"sizeBytes":4250},{"name":"05.column-visibility.md","path":"skills/data-table/references/3.columns/05.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/05.column-visibility.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F05.column-visibility.md -->","frontmatter":{"title":"Column Visibility","description":"Hide and restore columns with declarative defaults, reactive controls, and persistence.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":1857,"sizeBytes":8403},{"name":"06.column-resizing.md","path":"skills/data-table/references/3.columns/06.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/06.column-resizing.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F06.column-resizing.md -->","frontmatter":{"title":"Column Resizing","description":"Let users change column widths with the opt-in resize model and shadcn resize handle.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":1888,"sizeBytes":8432},{"name":"07.column-reordering.md","path":"skills/data-table/references/3.columns/07.column-reordering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/07.column-reordering.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F07.column-reordering.md -->","frontmatter":{"title":"Column Reordering","description":"Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.","sidebar":{"label":"Column Reordering"}},"isInternal":false,"tokens":1822,"sizeBytes":7997},{"name":"08.runtime-columns.md","path":"skills/data-table/references/3.columns/08.runtime-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/08.runtime-columns.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F08.runtime-columns.md -->","frontmatter":{"title":"Generating Columns at Runtime","description":"Discover and declare table columns when the field list is only known after data arrives.","sidebar":{"label":"Runtime Columns"}},"isInternal":false,"tokens":2071,"sizeBytes":9389},{"name":"09.column-layout.md","path":"skills/data-table/references/3.columns/09.column-layout.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/09.column-layout.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2F09.column-layout.md -->","frontmatter":{"title":"Column Layout","description":"Set base column widths and choose which columns grow into extra horizontal space.","sidebar":{"label":"Column Layout"}},"isInternal":false,"tokens":988,"sizeBytes":4773},{"name":"index.md","path":"skills/data-table/references/3.columns/index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/3.columns/index.md","title":"3.columns Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F3.columns%2Findex.md -->","frontmatter":{"title":"Overview","description":"Define the visible columns, headers, and cells that turn model rows into a grid.","sidebar":{"order":0,"label":"Overview"}},"isInternal":false,"tokens":1096,"sizeBytes":4897},{"name":"4.grouped-rows.md","path":"skills/data-table/references/4.grouped-rows.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/4.grouped-rows.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F4.grouped-rows.md -->","frontmatter":{"title":"Grouped Rows","description":"Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":2792,"sizeBytes":11637},{"name":"5.state-persistence.md","path":"skills/data-table/references/5.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/5.state-persistence.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F5.state-persistence.md -->","frontmatter":{"title":"State Persistence","description":"Persist table feature state and model action state with opt-in adapters.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1898,"sizeBytes":8940},{"name":"6.controlling-the-table.md","path":"skills/data-table/references/6.controlling-the-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/6.controlling-the-table.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F6.controlling-the-table.md -->","frontmatter":{"title":"Controlling the Table","description":"Read table state and publish table actions from nearby toolbars, panels, and other application UI.","sidebar":{"label":"Controlling the Table"}},"isInternal":false,"tokens":2775,"sizeBytes":12134},{"name":"01.styling.md","path":"skills/data-table/references/7.customization/01.styling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/01.styling.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F01.styling.md -->","frontmatter":{"title":"Styling Cells, Headers, and Rows","description":"Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.","sidebar":{"label":"Styling"}},"isInternal":false,"tokens":1218,"sizeBytes":5629},{"name":"02.replacing-internals.md","path":"skills/data-table/references/7.customization/02.replacing-internals.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/02.replacing-internals.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F02.replacing-internals.md -->","frontmatter":{"title":"Replacing Internals","description":"Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.","sidebar":{"label":"Replacing Internals"}},"isInternal":false,"tokens":1043,"sizeBytes":4738},{"name":"03.empty-and-loading-states.md","path":"skills/data-table/references/7.customization/03.empty-and-loading-states.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/03.empty-and-loading-states.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F03.empty-and-loading-states.md -->","frontmatter":{"title":"Empty, Loading, and Error States","description":"Customize empty placeholders and remote loading states without changing data model.","sidebar":{"label":"Empty and Loading States"}},"isInternal":false,"tokens":815,"sizeBytes":3693},{"name":"04.scroll-containers.md","path":"skills/data-table/references/7.customization/04.scroll-containers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/04.scroll-containers.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F04.scroll-containers.md -->","frontmatter":{"title":"Scroll Containers","description":"Attach the table to a window, parent element, or custom scroll element when the default table scroller isn't enough.","sidebar":{"label":"Scroll Containers"}},"isInternal":false,"tokens":1059,"sizeBytes":4654},{"name":"05.ambient-context.md","path":"skills/data-table/references/7.customization/05.ambient-context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/05.ambient-context.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F05.ambient-context.md -->","frontmatter":{"title":"Ambient Context","description":"A single bag of values, available throughout the table's customizable parts — placeholders, loading components, the scroll element, `computeRowKey`, and structural component overrides.","sidebar":{"label":"Ambient Context"}},"isInternal":false,"tokens":860,"sizeBytes":4059},{"name":"06.header-slots.md","path":"skills/data-table/references/7.customization/06.header-slots.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/06.header-slots.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F06.header-slots.md -->","frontmatter":{"title":"Header Slots","description":"Mount sort buttons, filter menus, resize handles, reorder grips, and other UI inside column headers through four shared slot positions.","sidebar":{"label":"Header Slots"}},"isInternal":false,"tokens":1389,"sizeBytes":6260},{"name":"07.shadcn-wrapper.md","path":"skills/data-table/references/7.customization/07.shadcn-wrapper.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/07.shadcn-wrapper.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F07.shadcn-wrapper.md -->","frontmatter":{"title":"Inside the shadcn Wrapper","description":"Understand what `npx shadcn add` wrote into your project, what lives in the headless package, and where it's safe to edit for app-wide defaults.","sidebar":{"label":"The shadcn Wrapper"}},"isInternal":false,"tokens":1570,"sizeBytes":7058},{"name":"08.card-mode.md","path":"skills/data-table/references/7.customization/08.card-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/7.customization/08.card-mode.md","title":"7.customization Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F7.customization%2F08.card-mode.md -->","frontmatter":{"title":"Card Mode","description":"Switch the same data model between table rows and an automatically measured grid of equal-sized cards.","sidebar":{"label":"Card Mode"}},"isInternal":false,"tokens":1607,"sizeBytes":7492},{"name":"01.basic-table.md","path":"skills/data-table/references/8.examples/01.basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/01.basic-table.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F01.basic-table.md -->","frontmatter":{"title":"Basic Table Example","description":"Start with a compact inventory table that uses the styled shadcn wrapper and a small product dataset.","sidebar":{"label":"Basic Table"}},"isInternal":false,"tokens":699,"sizeBytes":2983},{"name":"03.grouped-data.md","path":"skills/data-table/references/8.examples/03.grouped-data.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/03.grouped-data.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F03.grouped-data.md -->","frontmatter":{"title":"Group Rows by Region and Category","description":"Organize a large inventory table into nested groups while preserving virtualization and sticky headers.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":1028,"sizeBytes":4285},{"name":"04.sticky-columns.md","path":"skills/data-table/references/8.examples/04.sticky-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/04.sticky-columns.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F04.sticky-columns.md -->","frontmatter":{"title":"Sticky Columns Example","description":"Pin identity and status columns so users can scan wide datasets without losing the row context.","sidebar":{"label":"Sticky Columns"}},"isInternal":false,"tokens":1048,"sizeBytes":4554},{"name":"06.column-reorder.md","path":"skills/data-table/references/8.examples/06.column-reorder.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/06.column-reorder.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F06.column-reorder.md -->","frontmatter":{"title":"Column Reorder Example","description":"Reorder columns with the shadcn drag handle and drop zone components.","sidebar":{"label":"Column Reorder"}},"isInternal":false,"tokens":649,"sizeBytes":2645},{"name":"07.local-filter-sort-group.md","path":"skills/data-table/references/8.examples/07.local-filter-sort-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/07.local-filter-sort-group.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F07.local-filter-sort-group.md -->","frontmatter":{"title":"Local Filter, Sort, Group","description":"Wire a filter dropdown, a sort dropdown, and a grouping dropdown to one local model so each control updates the rows without refetching.","sidebar":{"label":"Local Filter, Sort, Group"}},"isInternal":false,"tokens":1862,"sizeBytes":7409},{"name":"08.remote-offset.md","path":"skills/data-table/references/8.examples/08.remote-offset.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/08.remote-offset.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F08.remote-offset.md -->","frontmatter":{"title":"Server-side Paging","description":"Fetch only the visible slice of rows for a large dataset while preserving filtering and total counts.","sidebar":{"label":"Paging"}},"isInternal":false,"tokens":1030,"sizeBytes":4524},{"name":"09.remote-append.md","path":"skills/data-table/references/8.examples/09.remote-append.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/09.remote-append.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F09.remote-append.md -->","frontmatter":{"title":"Endless Scrolling","description":"Append rows to the table as the user scrolls toward the bottom, using a cursor-based fetch function and append-mode bookkeeping.","sidebar":{"label":"Endless Scrolling"}},"isInternal":false,"tokens":835,"sizeBytes":3582},{"name":"10.table-instance-dashboard.md","path":"skills/data-table/references/8.examples/10.table-instance-dashboard.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/10.table-instance-dashboard.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F10.table-instance-dashboard.md -->","frontmatter":{"title":"External Toolbar","description":"Pair a control panel with a table instance and publish scroll and layout actions through the reactive engine.","sidebar":{"label":"External Toolbar"}},"isInternal":false,"tokens":1181,"sizeBytes":4946},{"name":"11.scrollbar-overlay.md","path":"skills/data-table/references/8.examples/11.scrollbar-overlay.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/11.scrollbar-overlay.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F11.scrollbar-overlay.md -->","frontmatter":{"title":"Keep Sticky Columns Clear of Overlay Scrollbars","description":"Keep sticky right columns readable on platforms that draw overlay scrollbars over the content.","sidebar":{"label":"Overlay Scrollbars"}},"isInternal":false,"tokens":741,"sizeBytes":3254},{"name":"12.custom-scroll-container.md","path":"skills/data-table/references/8.examples/12.custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/12.custom-scroll-container.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F12.custom-scroll-container.md -->","frontmatter":{"title":"Embed a Table in a Shared Scroll Area","description":"Attach the table to an existing scroll parent so headers, filters, and supporting UI can share one scroll viewport.","sidebar":{"label":"Shared Scroll Area"}},"isInternal":false,"tokens":812,"sizeBytes":3558},{"name":"13.column-resizing.md","path":"skills/data-table/references/8.examples/13.column-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/13.column-resizing.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F13.column-resizing.md -->","frontmatter":{"title":"Column Resizing Example","description":"Add resize handles and external width controls to a table.","sidebar":{"label":"Column Resizing"}},"isInternal":false,"tokens":942,"sizeBytes":4093},{"name":"14.column-visibility.md","path":"skills/data-table/references/8.examples/14.column-visibility.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/14.column-visibility.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F14.column-visibility.md -->","frontmatter":{"title":"Column Visibility Example","description":"Build a toolbar that hides and restores columns at runtime.","sidebar":{"label":"Column Visibility"}},"isInternal":false,"tokens":937,"sizeBytes":4095},{"name":"15.state-persistence.md","path":"skills/data-table/references/8.examples/15.state-persistence.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/15.state-persistence.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F15.state-persistence.md -->","frontmatter":{"title":"State Persistence Example","description":"Persist a remote filter action and column widths for a remote table.","sidebar":{"label":"State Persistence"}},"isInternal":false,"tokens":1172,"sizeBytes":5252},{"name":"16.remote-column-sorting.md","path":"skills/data-table/references/8.examples/16.remote-column-sorting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/8.examples/16.remote-column-sorting.md","title":"8.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F8.examples%2F16.remote-column-sorting.md -->","frontmatter":{"title":"Remote Column Sorting","description":"Use shadcn header buttons to send sort params to a remote data model.","sidebar":{"label":"Remote Sorting"}},"isInternal":false,"tokens":1429,"sizeBytes":6164},{"name":"01.testing.md","path":"skills/data-table/references/9.guides/01.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/9.guides/01.testing.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F01.testing.md -->","frontmatter":{"title":"Testing","description":"Make table behavior deterministic in tests by providing explicit test dimensions.","sidebar":{"label":"Testing"}},"isInternal":false,"tokens":631,"sizeBytes":2858},{"name":"02.performance.md","path":"skills/data-table/references/9.guides/02.performance.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/9.guides/02.performance.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F02.performance.md -->","frontmatter":{"title":"Performance","description":"Keep render work predictable with stable keys, deliberate overscan, and bounded viewport rendering.","sidebar":{"label":"Performance"}},"isInternal":false,"tokens":734,"sizeBytes":3669},{"name":"03.troubleshooting.md","path":"skills/data-table/references/9.guides/03.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/9.guides/03.troubleshooting.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F03.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting","description":"Diagnose the most common integration issues around layout, imports, and scroll configuration.","sidebar":{"label":"Troubleshooting"}},"isInternal":false,"tokens":788,"sizeBytes":4194},{"name":"04.migrating-from-table-virtuoso.md","path":"skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/9.guides/04.migrating-from-table-virtuoso.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F04.migrating-from-table-virtuoso.md -->","frontmatter":{"title":"Migrating From TableVirtuoso","description":"Map common TableVirtuoso patterns to the data-table package.","sidebar":{"label":"Migrating From TableVirtuoso"}},"isInternal":false,"tokens":890,"sizeBytes":3935},{"name":"05.debug-instrumentation.md","path":"skills/data-table/references/9.guides/05.debug-instrumentation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/9.guides/05.debug-instrumentation.md","title":"9.guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2F9.guides%2F05.debug-instrumentation.md -->","frontmatter":{"title":"Inspecting Row Re-renders","description":"Enable unstable row render events while investigating rendering behavior.","sidebar":{"label":"Row Renders"}},"isInternal":false,"tokens":1203,"sizeBytes":5047},{"name":"README.md","path":"skills/data-table/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/references/README.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1289,"sizeBytes":5531},{"name":"SKILL.md","path":"skills/data-table/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/data-table/SKILL.md","title":"data-table","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fdata-table%2FSKILL.md -->","frontmatter":{"name":"data-table","description":"Build virtualized data tables with @virtuoso.dev/data-table. Use this skill when (1) building a data grid with sorting, filtering, or grouped rows, (2) installing the shadcn-styled or headless table, (3) connecting remote/paginated data sources, (4) adding sticky, resizable, reorderable, or hideable columns, (5) persisting table state, (6) controlling a table from outside (scrolling, actions), (7) migrating from TableVirtuoso, or any task involving VirtuosoDataTable, DataTable, DataTableColumn, localModel, remoteModel, or engine refs like scrollToRow$ and dispatchModelAction$."},"isInternal":false,"tokens":4561,"sizeBytes":21050},{"name":"10.smooth-scrolling.md","path":"skills/message-list/references/10.smooth-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/10.smooth-scrolling.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F10.smooth-scrolling.md -->","frontmatter":{"id":"virtuoso-message-list-smooth-scrolling","title":"Virtuoso Message Custom Smooth Scrolling","description":"Customize scroll animations with custom easing functions like bounce or elastic for unique chat UX.","sidebar_label":"Custom Smooth Scrolling","sidebar_position":8,"slug":"/virtuoso-message-list/smooth-scrolling"},"isInternal":false,"tokens":1403,"sizeBytes":5608},{"name":"11.testing.md","path":"skills/message-list/references/11.testing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/11.testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F11.testing.md -->","frontmatter":{"id":"virtuoso-message-list-testing","title":"Virtuoso Message List Testing","description":"Test VirtuosoMessageList with Playwright E2E or React Testing Library using VirtuosoMessageListTestingContext.","sidebar_label":"Testing","sidebar_position":9,"slug":"/virtuoso-message-list/testing"},"isInternal":false,"tokens":478,"sizeBytes":2081},{"name":"15.imperative-data-api.md","path":"skills/message-list/references/15.imperative-data-api.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/15.imperative-data-api.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F15.imperative-data-api.md -->","frontmatter":{"id":"virtuoso-message-list-working-with-data","title":"Virtuoso Message List - Working with Data","description":"Use imperative data API for append, prepend, map, and delete operations with automatic scroll handling.","sidebar_label":"Imperative Data API","sidebar_position":10,"slug":"/virtuoso-message-list/working-with-data"},"isInternal":false,"tokens":3508,"sizeBytes":15613},{"name":"19.resize-observer-errors.md","path":"skills/message-list/references/19.resize-observer-errors.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/19.resize-observer-errors.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F19.resize-observer-errors.md -->","frontmatter":{"id":"virtuoso-message-list-resize-observer-errors","title":"Virtuoso Message List Resize Observer Errors","description":"Handle benign ResizeObserver loop errors in Webpack dev server and Sentry error tracking.","sidebar_label":"Resize Observer Errors","sidebar_position":10,"slug":"/virtuoso-message-list/resize-observer-errors"},"isInternal":false,"tokens":333,"sizeBytes":1609},{"name":"01.intro.md","path":"skills/message-list/references/2.tutorial/01.intro.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/01.intro.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F01.intro.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-intro","title":"Virtuoso Message List Tutorial - Intro","description":"Get started building a chat UI with VirtuosoMessageList - set up the project and define message data structures.","sidebar_label":"Intro","sidebar_position":1,"slug":"/virtuoso-message-list/tutorial/intro"},"isInternal":false,"tokens":865,"sizeBytes":3697},{"name":"02.message-list.md","path":"skills/message-list/references/2.tutorial/02.message-list.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/02.message-list.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F02.message-list.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-message-list","title":"Virtuoso Message List Tutorial - Message List","description":"Add the VirtuosoMessageList component to your app with custom ItemContent and context for chat message rendering.","sidebar_label":"Message List","sidebar_position":2,"slug":"/virtuoso-message-list/tutorial/message-list"},"isInternal":false,"tokens":2551,"sizeBytes":11018},{"name":"03.loading-older-messages.md","path":"skills/message-list/references/2.tutorial/03.loading-older-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/03.loading-older-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F03.loading-older-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-loading-older-messages","title":"Virtuoso Message List Tutorial - Loading Older Messages","description":"Implement infinite scroll to load chat history when users scroll up using onScroll and prepend scroll modifier.","sidebar_label":"Loading Older Messages","sidebar_position":3,"slug":"/virtuoso-message-list/tutorial/loading-older-messages"},"isInternal":false,"tokens":877,"sizeBytes":3862},{"name":"04.scroll-to-bottom-button.md","path":"skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/04.scroll-to-bottom-button.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F04.scroll-to-bottom-button.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-scroll-to-bottom-button","title":"Virtuoso Message List Tutorial - Scroll to Bottom Button","description":"Add a scroll-to-bottom button using StickyFooter, useVirtuosoLocation and useVirtuosoMethods hooks.","sidebar_label":"Scroll to Bottom Button","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/scroll-to-bottom-button"},"isInternal":false,"tokens":701,"sizeBytes":3040},{"name":"05.receive-messages.md","path":"skills/message-list/references/2.tutorial/05.receive-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/05.receive-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F05.receive-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-receive-messages","title":"Virtuoso Message List Tutorial - Receive Messages","description":"Handle incoming messages with auto-scroll-to-bottom modifier and display unread message indicators.","sidebar_label":"Receive Messages","sidebar_position":4,"slug":"/virtuoso-message-list/tutorial/receive-messages"},"isInternal":false,"tokens":1097,"sizeBytes":4957},{"name":"06.send-messages.md","path":"skills/message-list/references/2.tutorial/06.send-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/06.send-messages.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F06.send-messages.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-send-messages","title":"Virtuoso Message List Tutorial - Send Messages","description":"Implement message sending with optimistic updates and delivery confirmation in VirtuosoMessageList.","sidebar_label":"Send Messages","sidebar_position":6,"slug":"/virtuoso-message-list/tutorial/send-messages"},"isInternal":false,"tokens":627,"sizeBytes":3016},{"name":"07.multiple-channels.md","path":"skills/message-list/references/2.tutorial/07.multiple-channels.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/2.tutorial/07.multiple-channels.md","title":"2.tutorial Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F2.tutorial%2F07.multiple-channels.md -->","frontmatter":{"id":"virtuoso-message-list-tutorial-multiple-channels","title":"Virtuoso Message List Tutorial - Multiple Channels","description":"Support multiple chat channels with independent scroll positions and message history in VirtuosoMessageList.","sidebar_label":"Multiple Channels","sidebar_position":7,"slug":"/virtuoso-message-list/tutorial/multiple-channels"},"isInternal":false,"tokens":625,"sizeBytes":2848},{"name":"20.scroll-modifier.md","path":"skills/message-list/references/20.scroll-modifier.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/20.scroll-modifier.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F20.scroll-modifier.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-modifier","title":"Virtuoso Message List Scroll Position Control","description":"Control VirtuosoMessageList scroll behavior with modifiers for prepend, append, item changes, and initial position.","sidebar_label":"Scroll Modifier","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-modifier"},"isInternal":false,"tokens":1563,"sizeBytes":7197},{"name":"21.controlled-mode-recipes.md","path":"skills/message-list/references/21.controlled-mode-recipes.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/21.controlled-mode-recipes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F21.controlled-mode-recipes.md -->","frontmatter":{"id":"virtuoso-message-list-controlled-mode-recipes","title":"Virtuoso Message List Controlled Mode Recipes","description":"Organize controlled message-list state behind app-owned actions for local sends, incoming messages, streaming updates, and row growth.","sidebar_label":"Controlled Mode Recipes","sidebar_position":11,"slug":"/virtuoso-message-list/controlled-mode-recipes"},"isInternal":false,"tokens":1348,"sizeBytes":6129},{"name":"01.messaging.md","path":"skills/message-list/references/3.examples/01.messaging.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/01.messaging.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F01.messaging.md -->","frontmatter":{"id":"virtuoso-message-list-examples-messaging","title":"Virtuoso Message List Examples - Messaging","description":"Complete chat messaging UI example with send, receive, optimistic updates, and multi-channel support.","sidebar_label":"Messaging Interface","sidebar_position":1,"slug":"/virtuoso-message-list/examples/messaging"},"isInternal":false,"tokens":2409,"sizeBytes":10356},{"name":"02.ai-chatbot.md","path":"skills/message-list/references/3.examples/02.ai-chatbot.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/02.ai-chatbot.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F02.ai-chatbot.md -->","frontmatter":{"id":"virtuoso-message-list-examples-ai-chatbot","title":"Virtuoso Message List Examples - AI Chatbot","description":"Build AI chatbot interfaces with streaming responses and smooth auto-scroll using VirtuosoMessageList.","sidebar_label":"AI Chatbot","sidebar_position":2,"slug":"/virtuoso-message-list/examples/ai-chatbot"},"isInternal":false,"tokens":1015,"sizeBytes":4504},{"name":"03.gemini.md","path":"skills/message-list/references/3.examples/03.gemini.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/03.gemini.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F03.gemini.md -->","frontmatter":{"id":"virtuoso-message-list-examples-gemini","title":"Virtuoso Message List Examples - Gemini-like Chatbot Scroll","description":"Implement Gemini-style chat scroll where questions align to top with streaming responses below.","sidebar_label":"Gemini-like Chatbot Scroll","sidebar_position":3,"slug":"/virtuoso-message-list/examples/gemini"},"isInternal":false,"tokens":1032,"sizeBytes":4584},{"name":"04.reactions.md","path":"skills/message-list/references/3.examples/04.reactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/04.reactions.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F04.reactions.md -->","frontmatter":{"id":"virtuoso-message-list-examples-reactions","title":"Virtuoso Message List Examples - Reactions","description":"Add emoji reactions to chat messages with proper scroll position handling using items-change modifier.","sidebar_label":"Reactions","sidebar_position":4,"slug":"/virtuoso-message-list/examples/reactions"},"isInternal":false,"tokens":1058,"sizeBytes":4392},{"name":"05.date-separators.md","path":"skills/message-list/references/3.examples/05.date-separators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/05.date-separators.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F05.date-separators.md -->","frontmatter":{"id":"virtuoso-message-list-examples-date-separators","title":"Virtuoso Message List Examples - Date Separators","description":"Display date separators between messages with sticky headers using prevData prop and useCurrentlyRenderedData hook.","sidebar_label":"Date Separators","sidebar_position":5,"slug":"/virtuoso-message-list/examples/date-separators"},"isInternal":false,"tokens":1082,"sizeBytes":4462},{"name":"06.scroll-to-reply.md","path":"skills/message-list/references/3.examples/06.scroll-to-reply.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/06.scroll-to-reply.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F06.scroll-to-reply.md -->","frontmatter":{"id":"virtuoso-message-list-examples-scroll-to-reply","title":"Virtuoso Message List Examples - Scroll to Reply","description":"Navigate to quoted messages with smooth scroll and highlight animation using scrollToItem and data.findIndex.","sidebar_label":"Scroll to Reply","sidebar_position":6,"slug":"/virtuoso-message-list/examples/scroll-to-reply"},"isInternal":false,"tokens":1330,"sizeBytes":5524},{"name":"07.grouped-messages.md","path":"skills/message-list/references/3.examples/07.grouped-messages.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/3.examples/07.grouped-messages.md","title":"3.examples Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F3.examples%2F07.grouped-messages.md -->","frontmatter":{"id":"virtuoso-message-list-examples-grouped-messages","title":"Virtuoso Message List Examples - Grouped Messages","description":"Group consecutive messages from the same user with connected bubbles using prevData and nextData props.","sidebar_label":"Grouped Messages","sidebar_position":7,"slug":"/virtuoso-message-list/examples/grouped-messages"},"isInternal":false,"tokens":1071,"sizeBytes":4720},{"name":"4.headers-footers.md","path":"skills/message-list/references/4.headers-footers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/4.headers-footers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F4.headers-footers.md -->","frontmatter":{"id":"virtuoso-message-list-headers-footers","title":"Virtuoso Message List Headers and Footers","description":"Add sticky and scrollable headers and footers to VirtuosoMessageList for loading indicators and overlays.","sidebar_label":"Headers and Footers","sidebar_position":5,"slug":"/virtuoso-message-list/headers-footers"},"isInternal":false,"tokens":1426,"sizeBytes":5588},{"name":"5.context.md","path":"skills/message-list/references/5.context.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/5.context.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F5.context.md -->","frontmatter":{"id":"virtuoso-message-list-context","title":"Virtuoso Message List Context","description":"Pass shared state to ItemContent and custom components without prop drilling using the context prop.","sidebar_label":"Context","sidebar_position":6,"slug":"/virtuoso-message-list/context"},"isInternal":false,"tokens":567,"sizeBytes":2477},{"name":"6.item-keys.md","path":"skills/message-list/references/6.item-keys.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/6.item-keys.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F6.item-keys.md -->","frontmatter":{"id":"virtuoso-message-list-item-keys","title":"Virtuoso Message List Item Keys","description":"Generate stable React keys for message list items using computeItemKey to prevent unnecessary remounts.","sidebar_label":"Item Keys","sidebar_position":6,"slug":"/virtuoso-message-list/item-keys"},"isInternal":false,"tokens":498,"sizeBytes":2187},{"name":"7.hooks.md","path":"skills/message-list/references/7.hooks.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/7.hooks.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F7.hooks.md -->","frontmatter":{"id":"virtuoso-message-list-hooks","title":"Virtuoso Message List Hooks","description":"Access message list methods and scroll state from child components using useVirtuosoMethods and useVirtuosoLocation.","sidebar_label":"Hooks","sidebar_position":7,"slug":"/virtuoso-message-list/hooks"},"isInternal":false,"tokens":496,"sizeBytes":2175},{"name":"8.custom-scrollbar.md","path":"skills/message-list/references/8.custom-scrollbar.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/8.custom-scrollbar.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F8.custom-scrollbar.md -->","frontmatter":{"id":"virtuoso-message-list-custom-scrollbar","title":"Virtuoso Message List Custom Scrollbar","description":"Integrate SimpleBar and other custom scrollbar libraries with VirtuosoMessageList using customScrollParent.","sidebar_label":"Custom Scrollbar Component","sidebar_position":30,"slug":"/virtuoso-message-list/custom-scrollbar"},"isInternal":false,"tokens":345,"sizeBytes":1487},{"name":"80.licensing.md","path":"skills/message-list/references/80.licensing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/80.licensing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F80.licensing.md -->","frontmatter":{"id":"virtuoso-message-list-licensing","title":"Virtuoso Message List Licensing","description":"Commercial license options, seat calculation, key setup, and trial terms for Virtuoso Message List.","sidebar_label":"Licensing","sidebar_position":3,"slug":"/virtuoso-message-list/licensing"},"isInternal":false,"tokens":1281,"sizeBytes":6112},{"name":"9.scrolling-to-item.md","path":"skills/message-list/references/9.scrolling-to-item.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/9.scrolling-to-item.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2F9.scrolling-to-item.md -->","frontmatter":{"id":"virtuoso-message-list-scroll-to-item","title":"Virtuoso Message List Scroll to Item","description":"Programmatically scroll to specific messages using the imperative scrollToItem API with alignment options.","sidebar_label":"Scroll to Item","sidebar_position":7,"slug":"/virtuoso-message-list/scroll-to-item"},"isInternal":false,"tokens":373,"sizeBytes":1553},{"name":"README.md","path":"skills/message-list/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/references/README.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1513,"sizeBytes":6493},{"name":"SKILL.md","path":"skills/message-list/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/message-list/SKILL.md","title":"message-list","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Fmessage-list%2FSKILL.md -->","frontmatter":{"name":"message-list","description":"Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up, (4) adding scroll-to-bottom buttons or unseen-message indicators, (5) switching between channels/conversations, or (6) any task involving VirtuosoMessageList, VirtuosoMessageListLicense, useVirtuosoMethods, useVirtuosoLocation, scrollModifier, notifyItemsChanged, scrollToBottomIfAtBottom, scrollToBottomAlways, smooth-scroll retargeting, replacement anchors, or data.append/prepend/map/replace. The package is commercial and requires a license key."},"isInternal":false,"tokens":3706,"sizeBytes":16510},{"name":"auto-resizing.md","path":"skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/auto-resizing.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fauto-resizing.md -->","frontmatter":{"title":"Auto Resizing Virtual List","description":"React Virtuoso automatically handles dynamic item height changes from content resizing, images loading, and container resizing.","sidebar":{"label":"Auto Resizing","order":2}},"isInternal":false,"tokens":341,"sizeBytes":1404},{"name":"basic-usage.md","path":"skills/react-virtuoso/references/1.virtuoso/basic-usage.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/basic-usage.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fbasic-usage.md -->","frontmatter":{"title":"Virtual List With 100,000 Items","description":"Get started with React Virtuoso by rendering a virtual list with 100,000 items of unknown sizes.","sidebar":{"label":"100,000 Items","order":1}},"isInternal":false,"tokens":590,"sizeBytes":2374},{"name":"custom-scroll-container.md","path":"skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/custom-scroll-container.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustom-scroll-container.md -->","frontmatter":{"title":"Customize Root Element","description":"Customize the React Virtuoso scroller element with custom styling, DOM event handlers, or integrate with custom scrollbar libraries.","position":500,"sidebar":{"label":"Customize Scroller"}},"isInternal":false,"tokens":487,"sizeBytes":2020},{"name":"customize-rendering.md","path":"skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/customize-rendering.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fcustomize-rendering.md -->","frontmatter":{"title":"Customize Rendering","description":"Replace React Virtuoso's default DOM elements with custom components for full control over list rendering and styling.","position":501,"sidebar":{"label":"Customize Rendering"}},"isInternal":false,"tokens":604,"sizeBytes":2500},{"name":"endless-scrolling.md","path":"skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/endless-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fendless-scrolling.md -->","frontmatter":{"title":"Endless Scrolling","description":"The React Virtuoso component makes it trivial to implement infinite scrolling lists in both directions with variably sized items.","sidebar":{"label":"Endless Scrolling","order":100}},"isInternal":false,"tokens":383,"sizeBytes":1584},{"name":"footer.md","path":"skills/react-virtuoso/references/1.virtuoso/footer.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/footer.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ffooter.md -->","frontmatter":{"title":"List with Footer Example","description":"Add a custom footer component to React Virtuoso lists for loading indicators, load more buttons, or end-of-list messages.","sidebar":{"label":"Footer","order":5}},"isInternal":false,"tokens":316,"sizeBytes":1317},{"name":"horizontal-mode.md","path":"skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/horizontal-mode.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fhorizontal-mode.md -->","frontmatter":{"title":"Horizontal Mode","description":"Render React Virtuoso lists horizontally with the horizontalDirection property for carousels and horizontal scrolling UIs.","sidebar_title":"Horizontal Mode","sidebar":{"order":90}},"isInternal":false,"tokens":321,"sizeBytes":1353},{"name":"initial-index.md","path":"skills/react-virtuoso/references/1.virtuoso/initial-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/initial-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Finitial-index.md -->","frontmatter":{"title":"Start from a certain item","description":"Use initialTopMostItemIndex to start React Virtuoso lists at a specific item position on mount.","sidebar":{"label":"Initial Index","order":4}},"isInternal":false,"tokens":222,"sizeBytes":903},{"name":"keyboard-navigation.md","path":"skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/keyboard-navigation.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fkeyboard-navigation.md -->","frontmatter":{"title":"Keyboard Navigation","description":"Implement keyboard navigation in React Virtuoso lists using the scrollIntoView method with arrow key support.","sidebar":{"label":"Keyboard Navigation","order":121}},"isInternal":false,"tokens":556,"sizeBytes":2337},{"name":"press-to-load-more.md","path":"skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/press-to-load-more.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fpress-to-load-more.md -->","frontmatter":{"title":"Press to Load More","description":"Add a load more button to React Virtuoso lists for user-triggered pagination instead of automatic infinite scrolling.","sidebar":{"label":"Press to Load More","order":101}},"isInternal":false,"tokens":536,"sizeBytes":2153},{"name":"range-change-callback.md","path":"skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/range-change-callback.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Frange-change-callback.md -->","frontmatter":{"title":"Range Change Callback","description":"Track visible item ranges in React Virtuoso lists using the rangeChanged callback for analytics or lazy loading.","sidebar":{"label":"Range Change Callback","order":101}},"isInternal":false,"tokens":297,"sizeBytes":1189},{"name":"scroll-handling.md","path":"skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/scroll-handling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-handling.md -->","frontmatter":{"title":"Scroll Handling","description":"Optimize React Virtuoso scroll performance by detecting scroll state and replacing heavy content with placeholders.","sidebar":{"label":"Scroll Handling","order":302}},"isInternal":false,"tokens":459,"sizeBytes":1879},{"name":"scroll-seek-placeholders.md","path":"skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/scroll-seek-placeholders.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-seek-placeholders.md -->","frontmatter":{"title":"Scroll Seek Placeholders","description":"Improve React Virtuoso fast scrolling performance by showing lightweight placeholders during rapid scroll.","sidebar":{"label":"Scroll Seek Placeholders","order":502}},"isInternal":false,"tokens":572,"sizeBytes":2458},{"name":"scroll-to-index.md","path":"skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/scroll-to-index.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fscroll-to-index.md -->","frontmatter":{"title":"Scroll to Index","description":"Programmatically scroll to any item in React Virtuoso using scrollToIndex with alignment and smooth scroll options.","sidebar":{"label":"Scroll to Index","order":50}},"isInternal":false,"tokens":650,"sizeBytes":2892},{"name":"tailwind-test.md","path":"skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/tailwind-test.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftailwind-test.md -->","frontmatter":{"title":"Tailwind Test"},"isInternal":false,"tokens":592,"sizeBytes":2316},{"name":"top-items.md","path":"skills/react-virtuoso/references/1.virtuoso/top-items.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/top-items.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Ftop-items.md -->","frontmatter":{"title":"Top Items List Example","description":"Pin the first N items at the top of React Virtuoso lists with the topItemCount property for sticky headers.","sidebar":{"label":"Top Items"}},"isInternal":false,"tokens":197,"sizeBytes":801},{"name":"window-scrolling.md","path":"skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/1.virtuoso/window-scrolling.md","title":"1.virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F1.virtuoso%2Fwindow-scrolling.md -->","frontmatter":{"title":"Window Scrolling","description":"Use the browser window as the scroll container for React Virtuoso lists with the useWindowScroll property.","sidebar":{"label":"Window Scrolling","order":250}},"isInternal":false,"tokens":143,"sizeBytes":543},{"name":"grouped-by-first-letter.md","path":"skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-by-first-letter.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-by-first-letter.md -->","frontmatter":{"title":"Grouped by First Letter","description":"Build alphabetically grouped lists with sticky headers using GroupedVirtuoso for contact lists and directories.","sidebar":{"label":"Grouped by First Letter","order":2}},"isInternal":false,"tokens":463,"sizeBytes":1946},{"name":"grouped-numbers.md","path":"skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-numbers.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-numbers.md -->","frontmatter":{"title":"Grouped 10,000 numbers","description":"Display large datasets in groups with sticky headers using React Virtuoso GroupedVirtuoso component.","sidebar":{"label":"Grouped Numbers","order":1}},"isInternal":false,"tokens":244,"sizeBytes":921},{"name":"grouped-with-load-on-demand.md","path":"skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/2.grouped-virtuoso/grouped-with-load-on-demand.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fgrouped-with-load-on-demand.md -->","frontmatter":{"title":"Grouped Load on Demand","description":"Implement infinite scrolling with grouped data in React Virtuoso by dynamically loading and appending groups.","sidebar":{"label":"Load on Demand","order":4}},"isInternal":false,"tokens":763,"sizeBytes":3281},{"name":"scroll-to-group.md","path":"skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/2.grouped-virtuoso/scroll-to-group.md","title":"2.grouped-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F2.grouped-virtuoso%2Fscroll-to-group.md -->","frontmatter":{"title":"Scroll to Group","description":"Build iOS-style contact lists with alphabetical navigation and scroll-to-group functionality in React Virtuoso.","sidebar":{"label":"Scroll to Group","order":5}},"isInternal":false,"tokens":624,"sizeBytes":2836},{"name":"grid-responsive-columns.md","path":"skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/3.virtuoso-grid/grid-responsive-columns.md","title":"3.virtuoso-grid Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F3.virtuoso-grid%2Fgrid-responsive-columns.md -->","frontmatter":{"title":"Grid with Responsive Columns","description":"Build responsive virtualized grids with VirtuosoGrid using CSS flexbox or grid for adaptive column layouts.","sidebar":{"label":"Responsive Columns"}},"isInternal":false,"tokens":593,"sizeBytes":2456},{"name":"basic-table.md","path":"skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/4.table-virtuoso/basic-table.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Fbasic-table.md -->","frontmatter":{"title":"Table Virtuoso Basic Example","description":"Virtualize large HTML tables with unknown row heights using TableVirtuoso for efficient rendering.","position":1,"sidebar":{"label":"Basic Example"}},"isInternal":false,"tokens":742,"sizeBytes":3007},{"name":"table-fixed-columns.md","path":"skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-columns.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-columns.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Columns","description":"Create horizontally scrollable tables with sticky frozen columns using TableVirtuoso and CSS positioning.","sidebar":{"label":"Fixed Columns"}},"isInternal":false,"tokens":431,"sizeBytes":1762},{"name":"table-fixed-headers.md","path":"skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/4.table-virtuoso/table-fixed-headers.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-fixed-headers.md -->","frontmatter":{"title":"Table Virtuoso Example with Fixed Headers","description":"Add sticky table headers that remain visible while scrolling using TableVirtuoso's fixedHeaderContent prop.","sidebar":{"label":"Fixed Headers"}},"isInternal":false,"tokens":307,"sizeBytes":1281},{"name":"table-grouped.md","path":"skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/4.table-virtuoso/table-grouped.md","title":"4.table-virtuoso Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F4.table-virtuoso%2Ftable-grouped.md -->","frontmatter":{"title":"Table Virtuoso Example with Grouped Rows","description":"Display grouped table data with sticky group headers using GroupedTableVirtuoso for organized data presentation.","sidebar":{"label":"Grouped Rows"}},"isInternal":false,"tokens":400,"sizeBytes":1621},{"name":"material-ui-endless-scrolling.md","path":"skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/5.third-party-integration/material-ui-endless-scrolling.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmaterial-ui-endless-scrolling.md -->","frontmatter":{"title":"MUI List with Endless Scrolling","description":"Use React Virtuoso with Material UI List components for virtualized grouped lists with MUI styling.","sidebar":{"label":"MUI List","order":1}},"isInternal":false,"tokens":255,"sizeBytes":1140},{"name":"mocking-in-tests.md","path":"skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/5.third-party-integration/mocking-in-tests.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmocking-in-tests.md -->","frontmatter":{"title":"Mocking in tests","description":"Test React Virtuoso components in Jest and Testing Library using VirtuosoMockContext to simulate DOM measurements.","sidebar":{"label":"Mocking in tests"}},"isInternal":false,"tokens":621,"sizeBytes":2380},{"name":"mui-table-virtual-scroll.md","path":"skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/5.third-party-integration/mui-table-virtual-scroll.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Fmui-table-virtual-scroll.md -->","frontmatter":{"title":"Table Virtuoso with MUI Table","description":"Integrate React Virtuoso TableVirtuoso with Material UI Table components for virtualized data grids.","sidebar":{"label":"MUI Table","order":1}},"isInternal":false,"tokens":316,"sizeBytes":1329},{"name":"tanstack-table-integration.md","path":"skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/5.third-party-integration/tanstack-table-integration.md","title":"5.third-party-integration Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F5.third-party-integration%2Ftanstack-table-integration.md -->","frontmatter":{"title":"Table Virtuoso integrated with @tanstack/table","description":"Combine TanStack Table's sorting and filtering with TableVirtuoso's virtualization for large datasets.","sidebar":{"label":"@tanstack/table"}},"isInternal":false,"tokens":260,"sizeBytes":1170},{"name":"6.troubleshooting.md","path":"skills/react-virtuoso/references/6.troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/6.troubleshooting.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2F6.troubleshooting.md -->","frontmatter":{"title":"Troubleshooting React Virtuoso","description":"Fix common React Virtuoso issues including margin problems, scroll jumping, zero-sized elements, and testing errors.","sidebar":{"label":"Troubleshooting","order":99}},"isInternal":false,"tokens":1291,"sizeBytes":5606},{"name":"README.md","path":"skills/react-virtuoso/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/references/README.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2Freferences%2FREADME.md -->","isInternal":false,"tokens":2409,"sizeBytes":9703},{"name":"SKILL.md","path":"skills/react-virtuoso/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/react-virtuoso/SKILL.md","title":"react-virtuoso","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freact-virtuoso%2FSKILL.md -->","frontmatter":{"name":"react-virtuoso","description":"Build virtualized lists, grids, and tables with react-virtuoso. Use this skill when (1) rendering large or infinite lists, (2) building grouped lists with sticky headers, (3) virtualizing HTML tables, (4) laying out same-sized items in a responsive grid, (5) building feeds or logs that follow new items at the bottom, (6) diagnosing virtualization symptoms such as jumpy scrolling, a list that does not scroll to the bottom, items overlapping, blank items, or \"zero-sized element\" errors, or (7) any task involving Virtuoso, GroupedVirtuoso, VirtuosoGrid, TableVirtuoso, VirtuosoHandle, itemContent, followOutput, or firstItemIndex."},"isInternal":false,"tokens":2976,"sizeBytes":13619},{"name":"01.core-concepts.md","path":"skills/reactive-engine/references/core/01.core-concepts.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/01.core-concepts.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F01.core-concepts.md -->","frontmatter":{"title":"Core Concepts","description":"Nodes, engines, and the separation between graph definitions and runtime state."},"isInternal":false,"tokens":1756,"sizeBytes":8056},{"name":"02.engine-and-lifecycle.md","path":"skills/reactive-engine/references/core/02.engine-and-lifecycle.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/02.engine-and-lifecycle.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F02.engine-and-lifecycle.md -->","frontmatter":{"title":"Engine and Lifecycle","description":"How engines activate nodes, the three API flavors, engine context, child engines, and disposal."},"isInternal":false,"tokens":1938,"sizeBytes":9457},{"name":"03.transactions.md","path":"skills/reactive-engine/references/core/03.transactions.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/03.transactions.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F03.transactions.md -->","frontmatter":{"title":"Transactions and Execution","description":"How publishing works - transactional cycles, topological ordering, and how the engine differs from RxJS."},"isInternal":false,"tokens":1312,"sizeBytes":6271},{"name":"04.operators-and-combinators.md","path":"skills/reactive-engine/references/core/04.operators-and-combinators.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/04.operators-and-combinators.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F04.operators-and-combinators.md -->","frontmatter":{"title":"Operators and Combinators","description":"Reference for transforming and connecting nodes."},"isInternal":false,"tokens":1651,"sizeBytes":8877},{"name":"05.building-on-the-engine.md","path":"skills/reactive-engine/references/core/05.building-on-the-engine.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/05.building-on-the-engine.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F05.building-on-the-engine.md -->","frontmatter":{"title":"Building on the Engine","description":"Architecture patterns for components and libraries built on the reactive engine, extracted from @virtuoso.dev/data-table."},"isInternal":false,"tokens":1670,"sizeBytes":7803},{"name":"06.diagnostics.md","path":"skills/reactive-engine/references/core/06.diagnostics.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/06.diagnostics.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2F06.diagnostics.md -->","frontmatter":{"title":"Propagation Diagnostics","description":"Observe structured engine propagation without retaining application state or changing graph behavior."},"isInternal":false,"tokens":2093,"sizeBytes":11216},{"name":"README.md","path":"skills/reactive-engine/references/core/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/core/README.md","title":"Core Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","isInternal":false,"tokens":760,"sizeBytes":3381},{"name":"01.queries-and-mutations.md","path":"skills/reactive-engine/references/query/01.queries-and-mutations.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/query/01.queries-and-mutations.md","title":"Query Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2F01.queries-and-mutations.md -->","frontmatter":{"title":"Queries and Mutations","description":"Data fetching as nodes - lifecycle, result states, retries, and wiring patterns."},"isInternal":false,"tokens":1171,"sizeBytes":4787},{"name":"README.md","path":"skills/reactive-engine/references/query/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/query/README.md","title":"Query Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","isInternal":false,"tokens":294,"sizeBytes":1178},{"name":"01.react-integration.md","path":"skills/reactive-engine/references/react/01.react-integration.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/react/01.react-integration.md","title":"React Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2F01.react-integration.md -->","frontmatter":{"title":"React Integration","description":"EngineProvider lifecycle, hooks, and remote engine access."},"isInternal":false,"tokens":2482,"sizeBytes":12587},{"name":"README.md","path":"skills/reactive-engine/references/react/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/react/README.md","title":"React Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","isInternal":false,"tokens":490,"sizeBytes":2092},{"name":"01.routing.md","path":"skills/reactive-engine/references/router/01.routing.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/router/01.routing.md","title":"Router Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2F01.routing.md -->","frontmatter":{"title":"Routing","description":"Typed routes as nodes, navigation, layouts, guards, and browser history."},"isInternal":false,"tokens":883,"sizeBytes":3610},{"name":"README.md","path":"skills/reactive-engine/references/router/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/router/README.md","title":"Router Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","isInternal":false,"tokens":358,"sizeBytes":1463},{"name":"01.storage-links.md","path":"skills/reactive-engine/references/storage/01.storage-links.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/storage/01.storage-links.md","title":"Storage Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2F01.storage-links.md -->","frontmatter":{"title":"Storage Links","description":"Persisting cells to localStorage, sessionStorage, and cookies."},"isInternal":false,"tokens":1242,"sizeBytes":5714},{"name":"README.md","path":"skills/reactive-engine/references/storage/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/references/storage/README.md","title":"Storage Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","isInternal":false,"tokens":190,"sizeBytes":756},{"name":"SKILL.md","path":"skills/reactive-engine/SKILL.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/skills/reactive-engine/SKILL.md","title":"reactive-engine","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=skills%2Freactive-engine%2FSKILL.md -->","frontmatter":{"name":"reactive-engine","description":"Manage application state with the @virtuoso.dev/reactive-engine-* package family. Use this skill when (1) defining reactive state with Cell, Stream, Trigger, or Resource nodes, (2) wiring React components through EngineProvider, useCellValue, useCellValues, or usePublisher, (3) fetching data with Query and Mutation from reactive-engine-query, (4) routing with Route, Layout, and Guard from reactive-engine-router, (5) persisting cells with linkCellToStorage, (6) architecting a component or library on top of the engine, (7) observing or troubleshooting propagation with describeNode, observeDiagnostics, useEngineDiagnostics, or useRemoteEngineDiagnostics, or (8) any task involving Engine, pub, sub, getValue, combine, pipe, link, changeWith, the e namespace, missing or duplicate emissions, unexpected distinct suppression, or the error \"No active engine found\"."},"isInternal":false,"tokens":3612,"sizeBytes":16566},{"name":"marketplace.json","path":".agents/plugins/marketplace.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.agents/plugins/marketplace.json","title":"virtuoso","category":"marketplace","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.agents%2Fplugins%2Fmarketplace.json -->","frontmatter":{"name":"virtuoso","interface":{"displayName":"Virtuoso"},"plugins":[{"name":"virtuoso-skills","source":{"source":"local","path":"./plugins/virtuoso-skills"},"policy":{"installation":"AVAILABLE","authentication":"ON_INSTALL"},"category":"Coding"}]},"isInternal":false,"tokens":106,"sizeBytes":370},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/.claude-plugin/marketplace.json","title":"virtuoso","category":"marketplace","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=.claude-plugin%2Fmarketplace.json -->","frontmatter":{"name":"virtuoso","owner":{"name":"Virtuoso.dev","email":"petyo@virtuoso.dev"},"description":"Skills for the Virtuoso family of React virtualization libraries","plugins":[{"name":"virtuoso-skills","source":"./packages/virtuoso-skills","description":"Agent guidance for react-virtuoso, message-list, data-table, and the reactive engine","homepage":"https://virtuoso.dev","repository":"https://github.com/petyosi/react-virtuoso","license":"MIT","keywords":["react","virtualization","table","chat"],"category":"frontend"}]},"isInternal":false,"tokens":182,"sizeBytes":634},{"name":"README.md","path":"packages/data-table/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/README.md","title":"data-table Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2FREADME.md -->","isInternal":false,"tokens":1289,"sizeBytes":5531},{"name":"README.md","path":"packages/data-table/src/features/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/data-table/src/features/README.md","title":"features Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fdata-table%2Fsrc%2Ffeatures%2FREADME.md -->","isInternal":false,"tokens":1123,"sizeBytes":4694},{"name":"README.md","path":"packages/gurx/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/gurx/README.md","title":"gurx Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fgurx%2FREADME.md -->","isInternal":false,"tokens":2002,"sizeBytes":8663},{"name":"README.md","path":"packages/masonry/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/masonry/README.md","title":"masonry Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmasonry%2FREADME.md -->","isInternal":false,"tokens":953,"sizeBytes":3508},{"name":"README.md","path":"packages/message-list/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/message-list/README.md","title":"message-list Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fmessage-list%2FREADME.md -->","isInternal":false,"tokens":1513,"sizeBytes":6493},{"name":"README.md","path":"packages/react-virtuoso/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/react-virtuoso/README.md","title":"react-virtuoso Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freact-virtuoso%2FREADME.md -->","isInternal":false,"tokens":2409,"sizeBytes":9703},{"name":"README.md","path":"packages/reactive-engine-core/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-core/README.md","title":"reactive-engine-core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-core%2FREADME.md -->","isInternal":false,"tokens":760,"sizeBytes":3381},{"name":"README.md","path":"packages/reactive-engine-query/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-query/README.md","title":"reactive-engine-query Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-query%2FREADME.md -->","isInternal":false,"tokens":294,"sizeBytes":1178},{"name":"README.md","path":"packages/reactive-engine-react/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-react/README.md","title":"reactive-engine-react Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-react%2FREADME.md -->","isInternal":false,"tokens":490,"sizeBytes":2092},{"name":"README.md","path":"packages/reactive-engine-router/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-router/README.md","title":"reactive-engine-router Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-router%2FREADME.md -->","isInternal":false,"tokens":358,"sizeBytes":1463},{"name":"README.md","path":"packages/reactive-engine-storage/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/reactive-engine-storage/README.md","title":"reactive-engine-storage Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Freactive-engine-storage%2FREADME.md -->","isInternal":false,"tokens":190,"sizeBytes":756},{"name":"plugin.json","path":"packages/virtuoso-skills/.claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/.claude-plugin/plugin.json","title":"virtuoso-skills","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2F.claude-plugin%2Fplugin.json -->","frontmatter":{"name":"virtuoso-skills","description":"Agent skills for react-virtuoso, message-list, data-table, and the reactive engine","version":"0.2.1","author":{"name":"Virtuoso.dev","email":"petyo@virtuoso.dev"},"homepage":"https://virtuoso.dev","repository":"https://github.com/petyosi/react-virtuoso","license":"MIT","keywords":["react-virtuoso","virtualization","table","chat","data-table","reactive-engine"]},"isInternal":false,"tokens":148,"sizeBytes":459},{"name":"README.md","path":"packages/virtuoso-skills/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/README.md","title":"virtuoso-skills Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2FREADME.md -->","isInternal":false,"tokens":498,"sizeBytes":2103},{"name":"README.md","path":"packages/virtuoso-skills/skills/data-table/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/data-table/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1289,"sizeBytes":5531},{"name":"README.md","path":"packages/virtuoso-skills/skills/message-list/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/message-list/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1513,"sizeBytes":6493},{"name":"README.md","path":"packages/virtuoso-skills/skills/react-virtuoso/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/react-virtuoso/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","isInternal":false,"tokens":2409,"sizeBytes":9703},{"name":"README.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/core/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/core/README.md","title":"core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","isInternal":false,"tokens":760,"sizeBytes":3381},{"name":"README.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/query/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/query/README.md","title":"query Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","isInternal":false,"tokens":294,"sizeBytes":1178},{"name":"README.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/react/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/react/README.md","title":"react Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","isInternal":false,"tokens":490,"sizeBytes":2092},{"name":"README.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/router/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/router/README.md","title":"router Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","isInternal":false,"tokens":358,"sizeBytes":1463},{"name":"README.md","path":"packages/virtuoso-skills/skills/reactive-engine/references/storage/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/packages/virtuoso-skills/skills/reactive-engine/references/storage/README.md","title":"storage Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=packages%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","isInternal":false,"tokens":190,"sizeBytes":756},{"name":"plugin.json","path":"plugins/virtuoso-skills/.codex-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/.codex-plugin/plugin.json","title":"virtuoso-skills","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2F.codex-plugin%2Fplugin.json -->","frontmatter":{"name":"virtuoso-skills","description":"Agent skills for react-virtuoso, message-list, data-table, and the reactive engine","version":"0.2.1","author":{"name":"Virtuoso.dev","email":"petyo@virtuoso.dev","url":"https://virtuoso.dev"},"homepage":"https://virtuoso.dev","repository":"https://github.com/petyosi/react-virtuoso","license":"MIT","keywords":["react-virtuoso","virtualization","table","chat","data-table","reactive-engine"],"skills":"./skills/","interface":{"displayName":"Virtuoso Skills","shortDescription":"Use Virtuoso React virtualization libraries correctly.","longDescription":"Agent guidance for react-virtuoso, message-list, data-table, and the reactive engine, generated from the official package docs.","developerName":"Virtuoso.dev","category":"Coding","capabilities":["Write"],"websiteURL":"https://virtuoso.dev","defaultPrompt":["Help me implement a virtualized list with react-virtuoso.","Help me build a message list with Virtuoso.","Help me use Virtuoso Data Table correctly."],"brandColor":"#1D4ED8"}},"isInternal":false,"tokens":334,"sizeBytes":1180},{"name":"README.md","path":"plugins/virtuoso-skills/skills/data-table/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/data-table/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fdata-table%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1289,"sizeBytes":5531},{"name":"README.md","path":"plugins/virtuoso-skills/skills/message-list/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/message-list/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Fmessage-list%2Freferences%2FREADME.md -->","isInternal":false,"tokens":1513,"sizeBytes":6493},{"name":"README.md","path":"plugins/virtuoso-skills/skills/react-virtuoso/references/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/react-virtuoso/references/README.md","title":"references Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freact-virtuoso%2Freferences%2FREADME.md -->","isInternal":false,"tokens":2409,"sizeBytes":9703},{"name":"README.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/core/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/core/README.md","title":"core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fcore%2FREADME.md -->","isInternal":false,"tokens":760,"sizeBytes":3381},{"name":"README.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/query/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/query/README.md","title":"query Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fquery%2FREADME.md -->","isInternal":false,"tokens":294,"sizeBytes":1178},{"name":"README.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/react/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/react/README.md","title":"react Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Freact%2FREADME.md -->","isInternal":false,"tokens":490,"sizeBytes":2092},{"name":"README.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/router/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/router/README.md","title":"router Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Frouter%2FREADME.md -->","isInternal":false,"tokens":358,"sizeBytes":1463},{"name":"README.md","path":"plugins/virtuoso-skills/skills/reactive-engine/references/storage/README.md","rawUrl":"https://raw.githubusercontent.com/petyosi/react-virtuoso/HEAD/plugins/virtuoso-skills/skills/reactive-engine/references/storage/README.md","title":"storage Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/petyosi/react-virtuoso?path=plugins%2Fvirtuoso-skills%2Fskills%2Freactive-engine%2Freferences%2Fstorage%2FREADME.md -->","isInternal":false,"tokens":190,"sizeBytes":756}],"systemPromptSnippet":"<yakaai_skills repo=\"petyosi/react-virtuoso\">\n<!-- File: AGENTS.md (Tokens: ~2044 | Category: root-instruction) -->\n# AGENTS.md\n\nRepo-specific guidance for agents working in `react-virtuoso`.\n\nUse this file for repo-specific navigation, validation, and documentation rules only. Keep it short, concrete, and limited to information that helps an agent make better decisions in this repository.\n\nIf a rule here appears to conflict with `package.json`, the actual filesystem, or a more specific instruction file in scope, trust the repo state and the more specific instruction.\n\n## High-Signal Rules\n\n- This is a `pnpm` workspaces monorepo. Prefer running commands from the repo root unless you need package-local scope.\n- Root `pnpm lint` does **not** run type checking. Run `pnpm typecheck` separately when you need type validation.\n- Internal working artifacts belong in Notion, not in repo markdown files.\n  - Use Notion for PRPs, plans, clarifications, execution notes, verification notes, prompts, investigations, reports, audits, and exported findings that are not product docs.\n  - Do not create or keep repo markdown artifacts in folders like `plans/`, `prompts/`, `reports/`, or similar ad hoc locations.\n  - Create new internal artifacts from the shared Notion template at `https://www.notion.so/3461834d8eff81318a4cccaf19b0de51` instead of creating free-form pages from scratch.\n  - The template's visual settings such as `Small text` and `Full width` are maintained manually in Notion. Preserve them by duplicating the template when possible.\n  - If the user asks for a PRP workflow, create or update the artifact in Notion instead of creating `plans/*.md`.\n  - If the user asks for a report or findings export, prefer a Notion page unless they explicitly ask for a repo file.\n  - Set a page icon that matches the artifact type or content when creating the page. Prefer clear, stable icons such as `📗` for PRPs/plans, `📊` for reports, `🔎` for investigations, `✍️` for prompts, and `🛠️` for execution notes.\n  - If an existing markdown file looks ambiguous, ask before deleting or migrating it.\n- Never use `data-testid` as a styling hook. It is reserved for tests. Use semantic attributes such as `data-table-element-role` instead.\n\n## NPM Registry Queries\n\nThis repo uses `devEngines.packageManager` with `\"onFail\": \"error\"` in the root `package.json`. `pnpm info` and `pnpm view` can fail because they delegate to npm and npm 11 rejects the request in this setup.\n\nUse npm from outside the repo instead:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Workspace Layout\n\n### Main packages\n\n- `packages/data-table` - virtualized data table package; currently the most active package in this repo\n- `packages/react-virtuoso` - main virtualization library\n- `packages/message-list` - chat/message list package\n- `packages/masonry` - masonry layout package\n- `packages/gurx` - reactive state library used by older Virtuoso internals\n- `packages/reactive-engine-*` - lower-level engine packages used by `data-table`\n\n### Apps and examples\n\n- `apps/virtuoso.dev` - Astro/Starlight docs site\n- `examples` - separate workspace for shared Ladle/integration examples\n- `packages/react-virtuoso/examples` - example pages used by `packages/react-virtuoso/e2e`\n\n## Plugin Distribution\n\n- This repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n- After merge, public Codex plugin install is:\n\n  ```bash\n  codex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\n  codex plugin add virtuoso-skills@virtuoso\n  ```\n\n- Source skill files live at `packages/virtuoso-skills/skills/<name>/SKILL.md`.\n- Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly. They are generated by `pnpm build:skills`.\n- Do not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Commands\n\n### Root\n\n- `pnpm build` - build all workspaces\n- `pnpm build:skills` - regenerate skill references and public skill mirrors\n- `pnpm validate:skills` - validate Claude and Codex plugin manifests\n- `pnpm typecheck` - run type checking across workspaces\n- `pnpm lint` - run package lint scripts across workspaces\n- `pnpm format` - format with `oxfmt`\n- `pnpm format:check` - check formatting with `oxfmt`\n- `pnpm test` - run workspace test scripts\n- `pnpm e2e` - run workspace e2e scripts\n- `pnpm lint:md` / `pnpm lint:md:fix` - markdown lint / fix\n- `pnpm ci` - full CI sequence: setup, build, typecheck, lint, markdown lint, test, e2e\n- `pnpm dev:docs` - start the docs site\n- `pnpm changeset-add` - add a changeset\n\n### `packages/data-table`\n\n- `pnpm build` - `tsc && vite build`\n- `pnpm typecheck` - `tsgo -b --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - `vitest run --browser.headless`\n- `pnpm check` - `format:check + lint + typecheck`\n- `pnpm dev` - run Ladle serve mode\n- `pnpm dev:build` / `pnpm dev:preview` - Ladle build / preview\n\n### `packages/react-virtuoso`\n\n- `pnpm build` - Vite build\n- `pnpm typecheck` - `tsgo --noEmit`\n- `pnpm lint` - `oxlint --type-aware --type-check`\n- `pnpm test` - Vitest\n- `pnpm test:watch` - Vitest watch\n- `pnpm e2e` - Playwright\n- `pnpm ladle` - preview examples in Ladle\n\n### `apps/virtuoso.dev`\n\n- `pnpm dev` - Astro dev server\n- `pnpm build` - `shadcn build && astro build`\n- `pnpm lint` - `oxlint --type-aware --type-check && astro check`\n- `pnpm format` / `pnpm format:check` - `oxfmt` plus Prettier for `.astro`\n\n## Documentation Workflow\n\nThe docs site contains generated package docs. Do not edit the generated content under:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nEdit the source docs in the package instead:\n\n- `packages/data-table/README.md` and `packages/data-table/docs/*.md`\n- `packages/react-virtuoso/README.md` and `packages/react-virtuoso/docs/*.md`\n- `packages/message-list/README.md` and `packages/message-list/docs/*.md`\n- `packages/masonry/README.md`\n- `packages/gurx/README.md`\n\nAfter product docs changes:\n\n- run `pnpm lint:md` from the repo root\n- if docs-site content or registry code changed, run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n\n## Validation Guidance\n\nChoose the narrowest validation that matches the change:\n\n- `packages/data-table` code changes: start with `pnpm check && pnpm test` in `packages/data-table`\n- `packages/react-virtuoso` code changes: run `pnpm lint && pnpm typecheck && pnpm test`, add `pnpm e2e` when behavior changes\n- docs-only markdown changes: run `pnpm lint:md`\n- docs site or registry changes: run `pnpm --filter @virtuoso.dev/virtuoso.dev lint`\n- broad cross-package changes: run root `pnpm typecheck && pnpm lint && pnpm test`\n\n## Architecture Notes\n\n- `packages/react-virtuoso` uses the custom `urx` stream/state system under `packages/react-virtuoso/src/urx`. If you touch its internals, follow existing `*System.ts` and `react-urx/` patterns.\n- `packages/data-table` is a separate package built on the `reactive-engine-*` workspaces. When working there, prefer matching existing engine/cell/stream patterns instead of borrowing directly from `react-virtuoso` internals.\n\n## Git Hooks\n\nThis repo uses `lefthook`. Pre-commit hooks format and lint staged files. If you need to skip them for a WIP commit:\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: ...\"\n```\n\n<!-- File: CLAUDE.md (Tokens: ~2756 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## NPM Registry Queries\n\nThis project uses `devEngines.packageManager` with `\"onFail\": \"error\"` in `package.json`, which causes `pnpm info` and `pnpm view` to fail because they delegate to npm, and npm 11 rejects the request. To check package versions, run npm from outside the project directory:\n\n```bash\n(cd /tmp && npm info <package> version)\n```\n\n## Build Commands\n\nThis is a pnpm workspaces monorepo. Run commands from the root or within specific workspace packages.\n\n### Root-level commands\n\nRun from repository root for all packages:\n\n- Build all: `pnpm build`\n- Lint all (includes type checking): `pnpm lint`\n- Format all: `pnpm format` (oxfmt)\n- Format check: `pnpm format:check`\n- Test all: `pnpm test`\n- E2E tests all: `pnpm e2e`\n- Markdown lint: `pnpm lint:md` / fix: `pnpm lint:md:fix`\n- Full CI: `pnpm ci` (setup, build, lint, lint:md, test, e2e)\n- Release: `pnpm release` (build + publish with changesets)\n- Add changeset: `pnpm changeset-add`\n- Dev docs site: `pnpm dev:docs`\n\n### react-virtuoso package (packages/react-virtuoso/)\n\n- Build: `pnpm run build` (uses vite)\n- Test: `pnpm run test` (vitest)\n- Test watch: `pnpm run test:watch`\n- Run single test: `pnpm vitest <test-file-path>` or `pnpm vitest -t \"<test-name>\"`\n- E2E tests: `pnpm run e2e` (playwright)\n- Lint (includes type checking): `pnpm run lint`\n- Format: `pnpm run format` (oxfmt)\n- Format check: `pnpm run format:check`\n- Dev/preview examples: `pnpm run ladle` (launches Ladle server for browsing examples/ folder)\n\n### virtuoso.dev docs app (apps/virtuoso.dev/)\n\n- Dev server: `pnpm run dev`\n- Build: `pnpm run build`\n- Format: `pnpm run format` (oxfmt + Prettier for .astro files)\n\nAfter docs changes: run `pnpm lint` from the app directory or root.\n\n**IMPORTANT - Documentation Locations:**\n\n**DO NOT EDIT** the following auto-generated directories:\n\n- `apps/virtuoso.dev/src/content/docs/data-table/`\n- `apps/virtuoso.dev/src/content/docs/react-virtuoso/`\n- `apps/virtuoso.dev/src/content/docs/masonry/`\n- `apps/virtuoso.dev/src/content/docs/gurx/`\n- `apps/virtuoso.dev/src/content/docs/message-list/`\n\nThese are auto-synced from source files + TypeDoc API via the `docsSync` integration. Any edits will be overwritten.\n\n**To edit package documentation**, modify the source files in each package:\n\n- **data-table**: `packages/data-table/docs/*.md`\n- **react-virtuoso**: `packages/react-virtuoso/README.md` or `packages/react-virtuoso/docs/*.md`\n- **masonry**: `packages/masonry/README.md` or `packages/masonry/docs/*.md`\n- **gurx**: `packages/gurx/README.md` or `packages/gurx/docs/*.md`\n- **message-list**: `packages/message-list/README.md` or `packages/message-list/docs/*.md`\n\n## Plugin Distribution\n\nThis repo ships `virtuoso-skills` as a Claude Code plugin from `packages/virtuoso-skills/`, a Codex plugin from `plugins/virtuoso-skills/`, and a root `skills/` mirror for `npx skills`.\n\nEdit source skill content in `packages/virtuoso-skills/skills/<name>/SKILL.md`. Do not edit `packages/virtuoso-skills/skills/*/references/`, root `skills/`, or `plugins/virtuoso-skills/skills/` directly; regenerate them with:\n\n```bash\npnpm build:skills\n```\n\nAfter merge, public Codex plugin install uses:\n\n```bash\ncodex plugin marketplace add petyosi/react-virtuoso --ref main --sparse .agents/plugins --sparse plugins/virtuoso-skills\ncodex plugin add virtuoso-skills@virtuoso\n```\n\nDo not commit `.agents/skills/` for Codex/OpenCode/Cursor unless the cross-agent install plan changes. `npx skills` owns that target path.\n\n## Monorepo Structure\n\n```text\npackages/\n  react-virtuoso/    - Main virtualization library\n  gurx/             - urx state management (fork/variant)\n  masonry/          - Masonry layout component\n  message-list/     - Chat/message list component\n  tooling/          - Shared build tooling\n\napps/\n  virtuoso.dev/     - Starlight/Astro documentation site\n\nexamples/            - Ladle stories for testing/development\n```\n\n## Architecture\n\n### State Management: urx System\n\nThe codebase uses **urx**, a custom reactive state management system built on streams/observables. Core concepts:\n\n- **Systems**: Stateful data-processing machines composed of streams\n- **Streams**: Can be stateless (signals) or stateful (depots that persist values)\n- **Depots**: Implicit state maintained in stateful streams, transformers (combineLatest), or operators (withLatestFrom, scan)\n- **Input/Output**: Systems receive input via input streams, process via transformers/operators, emit via output streams\n\nKey urx files: `src/urx/` directory contains:\n\n- `system.ts` - System creation and composition\n- `streams.ts` - Stream primitives\n- `pipe.ts` - Stream operators and transformers\n- `actions.ts` - Publishing/emitting\n- `transformers.ts` - Stream transformation utilities\n\n### Component Architecture\n\nThe virtualization logic is split into modular systems in `src/`:\n\n**Core Systems:**\n\n- `listSystem.ts` - Composes all feature systems into the main list system\n- `sizeSystem.ts` - Tracks and manages item sizes (critical for variable-height items)\n- `listStateSystem.ts` - Manages visible item ranges and scrolling state\n- `domIOSystem.ts` - DOM measurements and interactions\n\n**Feature Systems:**\n\n- `groupedListSystem.ts` - Grouped lists with sticky headers\n- `scrollToIndexSystem.ts` - Programmatic scroll positioning\n- `followOutputSystem.ts` - Auto-scroll for chat/feed UIs\n- `initialTopMostItemIndexSystem.ts` - Initial scroll position\n- `scrollSeekSystem.ts` - Placeholder rendering during fast scrolling\n- `windowScrollerSystem.ts` - Window-scrolling mode\n- And many more in `src/*System.ts` files\n\n**React Integration:**\n\n- `react-urx/` - Bridges urx systems to React components\n- `Virtuoso.tsx` - Main list component\n- `VirtuosoGrid.tsx` - Grid layout component\n- `TableVirtuoso.tsx` - Table virtualization component\n- Component interfaces in `component-interfaces/`\n\n### Size Calculation\n\nVariable-sized items work automatically via `sizeSystem.ts`:\n\n- Uses ResizeObserver for measurements\n- Maintains size ranges and estimates\n- No manual height specification needed\n- `correctItemSize()` utility in `utils/` handles size corrections\n\n### E2E Testing\n\nE2E tests in `packages/react-virtuoso/e2e/`:\n\n- Test files: `*.test.ts` (Playwright tests)\n- Example pages: `examples/*.tsx` (rendered in browser for tests)\n- Use Ladle (`pnpm run ladle`) to preview examples during development\n\n## Code Style\n\n- Never use `data-testid` attributes for CSS styling selectors. `data-testid` is reserved for testing only. Use `data-table-element-role` or other semantic data attributes for styling hooks.\n- TypeScript with strong typing; avoid `any`\n- oxfmt: 140 char width, single quotes, no semicolons\n- Naming: camelCase for variables/functions, PascalCase for components\n- Imports: React first, external libs, then internal modules\n- Functional components with hooks preferred\n- Use urx system patterns for state management\n- Error handling: prefer early returns\n\n## Markdown Style Guide\n\nWhen writing or editing markdown documentation:\n\n### Formatting Rules\n\n- Headings: ATX-style (`# Heading` not `Heading\\n=======`)\n- Code blocks: Always use fenced blocks with language specifiers\n\n```typescript\nconst foo = 'bar'\n```\n\n- Lists: Use `-` for unordered lists, indent nested items by 2 spaces\n- Emphasis: Use `_single underscore_` for emphasis, `**double asterisk**` for strong\n- Links: Prefer inline links `[text](url)` for readability\n\n### Content Guidelines\n\n- Start with clear, descriptive headings (H1 for title, H2 for major sections)\n- Use code blocks for all code examples, terminal commands, and file paths\n- Include language identifiers in fenced code blocks (`typescript`, `bash`, `json`)\n- Break long paragraphs into shorter ones (3-5 sentences max)\n- Use tables for structured data comparison\n- Add blank lines before and after headings, lists, code blocks, and tables\n\n### Special Cases\n\n- Inline HTML allowed for badges, complex layouts, or special formatting\n- Bare URLs allowed in reference sections and changelogs\n- Line length not enforced (practical for existing docs)\n- Multiple H1 headings allowed (document sections)\n\n### Linting\n\n- Run `pnpm lint:md` to check markdown files\n- Run `pnpm lint:md:fix` to auto-fix issues\n- Pre-commit hooks automatically lint staged .md files\n- Configuration: `.markdownlint.json` and `.markdownlintignore`\n- If necessary, use `markdownlint` CLI directly, but prefer pnpm scripts\n\n## Code Change Checklist\n\nAfter making code changes, run these commands to verify quality:\n\n### Required (always run)\n\n- `pnpm lint` - Lint and type check (oxlint --type-aware --type-check)\n- `pnpm format` - Format code with oxfmt\n- `pnpm test` - Run unit tests (vitest)\n\n### Conditionally Required\n\n- `pnpm lint:md` - If editing markdown files\n- `pnpm lint:md:fix` - Auto-fix markdown issues\n- `pnpm run e2e` - For UI/behavior changes (Playwright tests)\n- `pnpm run ladle` - To visually inspect component changes\n\n### Quick Full Validation\n\n- `pnpm ci` - Run complete CI pipeline (setup, build, lint, lint:md, test, e2e)\n- `pnpm format:check` - Check if files are formatted without modifying them\n\n### Fixing Issues\n\nFormat issues are auto-fixed by `pnpm format`. oxlint issues must be fixed manually. Configure your editor to:\n\n- Format on save using oxfmt (140 char width, single quotes, no semicolons)\n- Show oxlint warnings/errors\n\nPre-commit hooks will block commits if lint (which includes type checking) fails.\n\n## Development Workflow\n\n1. Make changes in `packages/react-virtuoso/src/`\n2. Run `pnpm format && pnpm lint && pnpm test`\n3. Check examples with `pnpm run ladle` if UI changes\n4. Run `pnpm e2e` for end-to-end validation if needed\n5. Add changeset with `pnpm changeset-add` for versioned changes\n\n## Git Hooks\n\nThis project uses [lefthook](https://github.com/evilmartians/lefthook) for git hooks.\n\n### Pre-commit Checks\n\nOn every commit, the following checks run automatically on staged files:\n\n- **Code formatting**: Formats `.ts/.tsx/.js/.jsx` files with oxfmt and `.astro` files with prettier; changes are auto-staged via `stage_fixed`\n- **Markdown linting**: Validates .md files with markdownlint\n- **Code linting**: Validates code files with oxlint\n\n### Skipping Hooks\n\nIf you need to skip hooks (e.g., WIP commits):\n\n```bash\nLEFTHOOK=0 git commit -m \"WIP: work in progress\"\n# Or use git commit --no-verify (not recommended)\n```\n\n### Hook Management\n\n- Configuration: `lefthook.json`\n- Install hooks: `pnpm exec lefthook install`\n- Uninstall hooks: `pnpm exec lefthook uninstall`\n- Run manually: `pnpm exec lefthook run pre-commit`\n\n<!-- File: .codex/skills/docs-guidance/agents/openai.yaml (Tokens: ~45 | Category: anthropic-skill) -->\ninterface:\n  display_name: 'Docs Guidance'\n  short_description: 'Write readable project docs'\n  default_prompt: 'Use $docs-guidance to revise this documentation for human readability, conceptual flow, and safe examples.'\n\n<!-- File: .codex/skills/docs-guidance/references/diagnostics.md (Tokens: ~1453 | Category: anthropic-skill) -->\n# Diagnostics: Invariants and Smells\n\nTwo complementary frameworks. Invariants describe what every page must teach. Smells describe what to look for when something feels off.\n\nConsult this file when reviewing existing prose, auditing a page against a standard, or deciding which rule to apply to a vague \"this feels off\" comment.\n\n## Documentation Invariants\n\nWhen revising a page, look for the invariant that matters most and make it explicit. Do not let a page merely say an API exists — it should explain the invariant the API protects.\n\n- **Ownership.** Where state, configuration, data, or styling should live.\n- **Identity.** Which keys, names, IDs, fields, or handles must remain stable.\n- **Shape.** The exact shape of data, config, events, return values, or rendered structure.\n- **Lifecycle.** What is created once, reused, reset, persisted, fetched, cancelled, or cleaned up.\n- **Boundary.** Which package, wrapper, component, hook, adapter, or integration owns each responsibility.\n- **Failure mode.** What breaks when the pattern is used in the wrong context.\n- **Escape hatch.** Which API, guide, or example to use when the simple pattern no longer fits.\n\nA page that names its API but does not teach one of these invariants is incomplete. The invariant is the load-bearing knowledge; the API is just the surface for invoking it.\n\n## Readability Failure Smells\n\nRevise when a page has any of these. Grouped by where the smell shows up.\n\n### Opening and framing\n\n- Starts with a library abstraction before explaining the user problem.\n- Begins a section as if the reader already accepted an unstated premise.\n- Opens a section by recommending the alternative the section is meant to compare against. The section is supposed to teach _this_ approach — defer the alternative to a sidebar at the end.\n- Limitation-focused section opens with the recipe instead of the bug the recipe prevents. Lead with the trap, then the fix.\n\n### Concepts and terminology\n\n- Uses a term that only makes sense after reading another page.\n- Drifts into odd phrases, private shorthand, or terms the reader would not naturally use.\n- The same identifier appears in the same doc with different shapes (e.g., two helpers named `fieldsFromRows` returning different types). Rename one to disambiguate.\n\n### Section purpose\n\n- Describes mechanics but not the reason the mechanic exists.\n- Says what is possible but not when to choose it.\n- Explains internal architecture when the user needs a decision rule.\n- The section's audience overlaps poorly with the parent page's audience — it likely belongs in a different page.\n- Has paragraphs that restate the previous paragraph's claim (\"This means…\", \"This keeps things X…\", \"In other words…\"). Meta-commentary that adds no new information; drop it.\n\n### Structure and navigation\n\n- Headings accurate but not helpful to a scanning reader. A heading should announce what the reader will learn, not just name the API.\n- Jumps from install or setup straight into advanced options without a \"what now\" bridge.\n- Cross-link text is vague (\"see X\"). The link text should answer \"why follow this.\" Prefer `[Generated columns](…) for type-aware columns` over `see [Generated columns](…)`.\n\n### Example quality\n\n- Presents \"correct\" code that would be unsafe in real usage.\n- Demonstrates a feature for dynamic or variable data using uniform example rows — silently hiding the variability question the feature exists to answer.\n\n### Feature pages\n\n- Page teaches the data contract or config object thoroughly but never shows what the end user can do with the feature. The reader closes the page knowing the shape but unable to picture the result or the interaction.\n- An interactive feature (the user clicks, drags, toggles, regroups, reorders) is demonstrated only as a static example. No control to click, no model action paired with UI. If the feature is interactive, the example must show the interaction.\n- Page leads with the data shape rather than what the end user sees. Almost always a sign that the writer let the cleanest-to-explain thing become the lead. The fix is to invert: end-user behavior first, contract last.\n- Interactive variations (Group By Category, Sort by Name) appear only as a bullet in a list of options, never as a worked example with the UI surface that fires them.\n\n### Framing tone\n\n- Uses defensive framing for things the library deliberately leaves to the user. (\"The package does not ship X\" → \"X lives in your code so the rules stay obvious next to the table.\")\n- Apologetic phrasing for intentional design choices (\"currently does not re-fetch\") instead of confident phrasing (\"intentionally one-shot\").\n- **Narrative motivation in openers.** Opens with what users want, feel, or expect instead of what the page documents. _\"Users expect headers to do work\"_, _\"A column header is rarely just a label\"_, _\"Wide tables push key context off-screen the moment a reader scrolls\"_ — these are scene-setting. The reader is here for the mechanism; preface drama is friction. Replace with a direct statement of the structure: _\"A column header has four slot positions for additional UI: …\"_.\n- **Anthropomorphism of inanimate code.** Gives UI parts intentions, conflicts, or effort. _\"fighting the label for space\"_, _\"the slots compete\"_, _\"the header expects\"_, _\"the column wants to be wider\"_. Replace with literal mechanics — _\"the slot is positioned outside the label's flex flow\"_ beats _\"the slot doesn't fight the label for space\"_. If the literal mechanic is unclear, the metaphor was hiding that the writer didn't know.\n- **Vibes enumeration.** Lists capabilities as atmospheric color rather than as commitments to coverage. _\"sort the column, open a filter menu, expose a resize handle, surface a 'Pin to left' affordance\"_ — reads like marketing. Either commit to which the page covers (and link out for the rest), or replace the list with the structural fact behind it (_\"Any UI that mounts into one of these four slots\"_). A list of nice-sounding capabilities the page won't actually teach is filler.\n- **\"Rarely just X\" / \"More than just X\" framings.** Opening with a denial of the obvious in order to motivate the topic. _\"A column header is rarely just a label\"_, _\"This isn't just a placeholder\"_, _\"Sticky columns are more than a CSS trick\"_. The reader didn't claim the thing was just X; the denial creates a fake premise the page then dismantles. Drop the denial and state what the thing is.\n\n## Triage rule\n\nIf a page exhibits multiple smells, fix the opening first. A confused opening cascades: every section after it is read through the wrong lens, so smells later in the page often dissolve once the lead is right.\n\n<!-- File: .codex/skills/docs-guidance/references/example-craft.md (Tokens: ~827 | Category: anthropic-skill) -->\n# Example Craft\n\nRules for the code examples themselves — how to construct them so they teach honestly without hiding the lifecycle, state ownership, or variability that real usage will hit.\n\nConsult this file when writing or auditing the code blocks inside a doc page (live demos, recipe snippets, illustrative JSX).\n\n## Production-Safe Defaults\n\nExamples should model the safe version, not a shortened one that hides the real constraints.\n\n- Use stable instances for stateful objects (no inline `new X()` in render).\n- Use stable identities (`key`, `id`) when items can move, persist, or be restored.\n- Include required cleanup, cancellation, or abort handling when async work is shown.\n- Keep generated data schemas deterministic.\n- Keep persistence examples clear about what is and is not saved.\n- Keep styling examples attached to the right layer: local content styling, wrapper styling, component overrides, or global CSS.\n- Avoid examples that are only short because they hide the lifecycle or state-ownership rule.\n\n## Demo Data Craftsmanship\n\nThe shape of the data matters as much as the code that processes it.\n\n- **Variable-shape data for dynamic-feature examples.** When demonstrating something that handles schema variability, the example rows must actually vary — some rows with extra fields, some without. Uniform example rows silently hide the question the feature exists to answer.\n- **Realistic field counts.** Three uniform columns make every feature look easy; six fields with mixed types stress the layout the same way real usage will.\n- **Schemas the reader recognises.** Use product/order/user shapes the reader has seen before. Resist the urge to invent fictional domains that need their own explanation.\n\n## Identifier Discipline Within a Doc\n\n- **Resolve identifier conflicts.** When you introduce a function, type, or prop name, scan the rest of the page for collisions. Two helpers named `fieldsFromRows` with different signatures in the same doc is a real confusion source. Rename one to disambiguate (`fieldsToShow`, `runtimeFields`, etc.).\n- **Inline helpers vs. canonical helpers.** When an example uses a slim inline helper because the canonical full helper would be overkill, name it differently so the reader can tell which one they're looking at.\n\n## Framing Tone\n\nThe words around the code carry the design's intent. Defensive or apologetic phrasing makes intentional choices look like missing features.\n\n- **Defensive to positive.** When the library deliberately doesn't ship a helper, reframe the absence as a benefit: _\"Field scanning lives in your app code so the rules stay obvious next to the table\"_ beats _\"The package does not inspect your row shape for you\"_.\n- **No apologies for design choices.** If the one-shot nature of a feature is intentional, say \"intentionally one-shot\" — not \"currently does not re-fetch\" (which sounds like a missing feature).\n- **Acknowledge limitations honestly.** When a constraint really is a limitation, name it as one and point at the workaround: _\"Offset placeholders cannot be distinguished from real rows. Use schema-compatible placeholders or backend-provided column metadata.\"_ Honest framing beats both apology and silence.\n\n## Component-Example Style\n\nPatterns that recur in component-library docs.\n\n- **`className` over wrapping `<div>`.** When a component accepts a `className`, prefer it to wrapping the render result in a styled `<div>`. Wrapping changes the DOM structure and breaks layout assumptions in many table, grid, and list contexts; using `className` keeps the wrapping consistent with non-example usage.\n- **Use the wrapper component the project ships.** If the project ships a shadcn-style wrapper (`DataTableCell`, `DataTableColumn`), use it in examples instead of the unwrapped primitive. The wrapped form is what users will copy; show that path.\n- **Skip the JSX import boilerplate in snippets.** Inline code blocks in the middle of prose are clearer when they show only the JSX under discussion. Save the full `import` list for live blocks that need to run.\n\n<!-- File: .codex/skills/docs-guidance/references/page-shapes.md (Tokens: ~1236 | Category: anthropic-skill) -->\n# Page Shapes and Section Shapes\n\nTemplates for whole pages (by type) and for recurring section shapes within pages.\n\nConsult this file when writing a new page from scratch, restructuring an existing page, or naming/ordering the sections of a page that already has its content roughed in.\n\n## Page Shapes by Type\n\n### Concept pages\n\nFor pages that introduce a single concept or primitive (a model, a hook, a low-level component). The audience is developers learning the primitive itself.\n\nIf the page describes a feature with a user-facing surface — grouping, filtering, drag-and-drop, sticky pinning, column reorder, visibility toggles — use the **Feature pages** template instead. Concept template is for pure-developer-facing primitives.\n\n- Open with the reader problem.\n- Explain the concept in ordinary words.\n- Show the data/state/API contract.\n- Show the smallest safe example.\n- Add runtime/lifecycle caveats.\n- Link to one deeper example or adjacent decision.\n\n### Feature pages\n\nFor pages that describe a user-facing capability — grouped rows, filtering, sorting, column reorder, column visibility, sticky pinning, drag-and-drop, expand/collapse. The developer reading the page is configuring something their end user will see or interact with.\n\nThe temptation in feature docs is to lead with the data shape or config object because it's the cleanest thing the writer understands. Resist it — the data shape is what makes the feature work, not what makes the feature interesting to the reader.\n\n- **What the end user sees.** Open with a sentence (or a small \"this is the result\" demo) describing the visible outcome.\n- **What the end user does.** If the feature is interactive, demonstrate the interaction with a live example that includes the UI control the user clicks, toggles, or drags. Static-only demos of interactive features are a smell — see [diagnostics.md](diagnostics.md#feature-pages).\n- **How to wire the interaction.** The API surface that exposes the behavior (model action, hook, prop) paired with the UI piece that triggers it. Treat the UI and the model wiring as one recipe; splitting them hides the half the reader came for.\n- **The data contract or config shape.** Only after the interaction has been shown. The contract is in service of the recipe above it.\n- **Variations.** Local vs remote, simple vs interactive, single-level vs multi-level — each as a sub-section that re-uses the user-behavior-first ordering.\n- **Limits and escape hatches.** Close with what doesn't fit and where to go next.\n\n### Example pages\n\nFor pages that demonstrate a recipe end-to-end.\n\n- First paragraph: \"This example uses X to solve Y.\"\n- `## APIs used` — short list grouped by job, not exhaustive reference.\n- Runnable example code.\n- Closing section named `## Notes`, `## When this doesn't fit`, `## When to outgrow this shape`, or `## What this leaves out`.\n\n### Troubleshooting and performance pages\n\n- Lead with symptoms and decision rules.\n- Prefer tables mapping symptom → fix.\n- Avoid implying users should tune defaults without a concrete symptom.\n\n### Installation pages\n\n- State what gets installed or generated.\n- Distinguish local editable code from package dependencies.\n- Show the smallest successful setup.\n- Link to the next required setup tasks.\n\n### Migration pages\n\n- State what is not a drop-in replacement.\n- Map old concepts to new concepts.\n- Give a recommended sequence, not a pile of independent facts.\n- End with the core mental-model shift.\n\n## Section Shapes (within pages)\n\nThese shapes recur across multiple page types — use them wherever they fit.\n\n### Limitation-focused section (gotcha-led)\n\nWhen a section exists because of a trap — \"Placeholders\" in a runtime-discovery page, \"Empty responses\" in a remote-data page, \"Schema drift\" in a persistence page:\n\n1. Open with the trap. Describe the bug the section's recipe prevents.\n2. Show the recipe.\n3. Add sidebar advice for cases the recipe still doesn't cover.\n\nExample opening: _\"The field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data.\"_ — then the schema-compatible placeholder recipe.\n\n### Comparison / \"When this does not fit\" section\n\nWhen enumerating alternatives at the end of a page, use **named bullets** with bold term labels:\n\n```markdown\n- **Backend-defined schemas.** Use when the backend already knows the shape.\n- **Statically-known schemas.** Use when the schema is in code but the JSX would be repetitive.\n- **Runtime column-management UI.** Lift the field list into your own state when the user can add or reorder fields after mount.\n```\n\nClose with a one-sentence summary of when the current page's approach _does_ fit.\n\n### Topic-first, alternative-second\n\nNever open a section by recommending the alternative it is meant to compare against. Pattern:\n\n1. Show the section's actual topic.\n2. After the example, add a sidebar paragraph for the alternative — \"When the backend can return column metadata alongside the rows, prefer that.\"\n\n### Parameter / config explanation block\n\nWhen showing what arrives at a render prop, hook, or callback, use bullets per parameter:\n\n```markdown\nThe render function is called with `{ data, model }`:\n\n- `data` is X — it does not change when Y.\n- `model` is Z — available for the rare case where W.\n```\n\nEasier to scan than parallel prose sentences.\n\n### \"How the contract looks\" before the example\n\nWhen behavior depends on a contract (data shape, config shape, event payload), render the shape inline before the runnable example:\n\n````markdown\nThe render function receives:\n\n```ts\n{\n  data: readonly Row[]\n  model: DataModelHandle<Row>\n}\n```\n\nThen the example…\n````\n\nThe shape grounds the example. Without it, the reader has to reverse-engineer the contract from the example body.\n\n<!-- File: .codex/skills/docs-guidance/SKILL.md (Tokens: ~1089 | Category: anthropic-skill) -->\n---\nname: docs-guidance\ndescription: Revises project documentation for human readability, conceptual flow, and example safety. Covers concept pages, example pages, troubleshooting, installation, and migration docs. Use when writing or reviewing docs, when the user says a page \"feels off\", \"feels weird\", \"feels sudden\", or \"needs attention\", when asked to make a doc clearer or to rewrite a section, when fixing cold or abrupt openings, when a section opens with the API mechanism instead of the user's problem, or when comparing prose against a readability standard.\n---\n\n# Docs Guidance\n\nUse this skill for project documentation work. Treat docs quality as a product concern: the reader should understand where they are, why the topic exists, what shape the relevant data or API has, and when the shown pattern stops fitting.\n\nThis skill is intentionally general. Apply it to package docs, guides, examples, troubleshooting pages, migration notes, and installation pages across the repo.\n\n## Workflow\n\n1. Start from the reader's task, decision, or confusion, not from the API list.\n2. Identify the core contract the page must teach: state ownership, data shape, lifecycle, render boundary, persistence keying, configuration shape, backend shape, or control surface.\n3. Rewrite around the path: user problem → concept → contract → runnable example → limits and next step.\n4. Keep examples safe for real usage, not just short.\n5. End with a concrete escape hatch: where to go when this example no longer fits.\n\n## Core Readability Rules\n\nThese are the non-negotiables. Most doc-quality problems trace to violating one of them:\n\n- **Start from the reader's situation.** Open each page with the user problem, decision, or concrete setup. Never begin with an unexplained abstraction or API name unless the page is explicit API reference.\n- **Describe, don't dramatize.** State what the thing is and what it does in literal terms. Don't open with what users feel or expect (\"users expect…\", \"users develop opinions about…\"); don't give code or UI intentions, conflicts, or effort (\"the slot fights for space\", \"the header expects to do work\"); don't enumerate capabilities as atmospheric color when the page only covers some of them. Narrative motivation, anthropomorphism, and vibes lists all signal the writer didn't commit to a direct statement of the mechanism. See [diagnostics.md → Framing tone](references/diagnostics.md#framing-tone) for the specific smells.\n- **Define concepts before using them.** Don't introduce project terms (`engine`, `slot`, `pipeline`, `marker`, `adapter`, `state cell`, `registry`, `provider`) without grounding them in the user's mental model first.\n- **Explain why before how.** Show the pressure that creates the API — rows need stable identity, requests can be superseded, persisted state needs a stable key — before showing the call signature.\n- **For feature pages, lead with user-facing behavior.** If a feature has an end-user surface (something the user sees, clicks, drags, toggles), demonstrate that surface — visibly, interactively when it's interactive — before the developer-facing config or data contract. The developer is reading the page to enable an end-user behavior; the end-user behavior is the topic, not the config that supports it.\n- **Show the contract just before the recipe it grounds.** When you're about to drop a code block whose behavior depends on a contract, render the data/state/config shape one paragraph before the code so the example doesn't have to be reverse-engineered. This is a within-section pattern, not a whole-page ordering rule — at page level, the user-facing behavior (for feature pages) or the reader's problem (for concept pages) comes first.\n- **End with a concrete next step.** Every section should exit with a pointer to the next decision, deeper example, or escape hatch.\n\n## When to consult deeper material\n\nLoad these references when the work calls for them. Keeping them out of this file is deliberate — load only what the current task needs.\n\n- **[references/diagnostics.md](references/diagnostics.md)** — the invariants framework (what every page must teach) and the failure smells (what to look for when reviewing). Consult when reviewing, diagnosing, or auditing existing prose.\n- **[references/page-shapes.md](references/page-shapes.md)** — templates for concept, example, troubleshooting, installation, migration, and limitation-focused pages, plus templates for section-level shapes (gotcha-led, comparison, topic-first-alternative-second). Consult when writing a new page or restructuring an existing one.\n- **[references/example-craft.md](references/example-craft.md)** — rules for the code examples themselves: stable instances, variable-shape demo data, identifier conflict resolution, defensive-to-positive framing, and styling-layer choices. Consult when writing or auditing example code blocks.\n\n## Final Check\n\nBefore finishing, confirm:\n\n- Can a reader understand the first paragraph without reading another page?\n- Are all public terms introduced before use?\n- Does every code example model safe state ownership and lifecycle?\n- Does every advanced mechanism explain the user pressure that creates it?\n- Does the page tell the reader what to do next when this pattern is not enough?\n\n<!-- File: packages/data-table/CLAUDE.md (Tokens: ~1448 | Category: anthropic-skill) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Development Commands\n\n- `pnpm build` - Build the library using TypeScript compiler and Vite\n- `pnpm lint` - Run linting with oxlint (type-aware)\n- `pnpm typecheck` - Run TypeScript type checking without emitting files\n- `pnpm test` - Run tests with Vitest (node and browser projects)\n- `pnpm dev` - Start Ladle development server for component stories\n- `pnpm docs:build` - Generate TypeDoc documentation\n- `pnpm docs:serve` - Serve generated documentation\n- `pnpm format` - Format code with oxfmt\n- `pnpm format:check` - Check formatting without modifying files\n\n## After Making Changes\n\nRun these commands after modifying code:\n\n1. `pnpm format` - Format modified files\n2. `pnpm lint` - Check for lint errors and fix any issues\n3. `pnpm typecheck` - Verify no TypeScript errors\n4. `pnpm test` - Run tests when changes affect component logic, state management, or virtualization behavior\n\nGit hooks (via Lefthook) automatically run formatting and linting on pre-commit, and typecheck + tests on pre-push.\n\n## Architecture Overview\n\nThis is a React virtualized data table component with both column and row virtualization. The architecture is based on a reactive state management system using `@virtuoso.dev/reactive-engine`.\n\n### Core Components\n\n- **VirtuosoDataTable** (`src/VirtuosoDataTable.tsx`) - Main React component that renders the virtualized data table\n- **VirtualizedTableContent** (`src/VirtualizedTableContent.tsx`) - Renders the table content with row and column virtualization\n- **Row** (`src/Row.tsx`) - Individual row renderer\n- **Column** (`src/Column.tsx`) - Column renderer with virtualization\n- **Cell** (`src/Cell.tsx`) - Cell renderer\n- **ColumnHeader** (`src/ColumnHeader.tsx`) - Column header renderer\n- **Data State** (`src/data.ts`) - Holds data cells (`data$`, `context$`, `totalCount$`)\n\n### State Management Architecture\n\nThe component uses a realm-based reactive system with cells:\n\n- **Cells** - Hold state values (e.g., `data$`, `context$`, `totalCount$`)\n- **Realm** - Manages the reactive graph and subscriptions\n\nKey state modules:\n\n- `src/data.ts` - Core data cells\n- `src/dom.ts` - DOM measurements and scroll handling\n- `src/row-state.ts` - Row rendering state management and visible row computation\n- `src/column-state.ts` - Column virtualization state\n- `src/sizes.ts` - Size tree management for virtualization\n- `src/state-flags.ts` - Scroll direction and bottom detection\n\n### Virtualization System\n\n- **Size Tree** (`src/sizing/AATree.ts`) - AA tree implementation for efficient size tracking\n- **Offset Calculations** (`src/sizing/` directory) - Handles item positioning and measurements\n- **Resize Observer** - Tracks item size changes dynamically\n- **Viewport Management** - Renders only visible items with buffer zones\n\n### Scroll Behavior Management\n\n- **Scroll-to-Row** (`src/scroll-to-row.ts`) - Handles programmatic scrolling to specific rows\n- **Smooth Scrolling** (`src/useSmoothScroll.ts`) - Custom smooth scroll implementation\n- **Mobile Safari Fixes** (`src/reverse-scroll-fix.ts`) - Platform-specific scroll behavior corrections\n\n### Testing Infrastructure\n\nTests are organized into two Vitest projects:\n\n- **Node Tests** - Pure logic tests in `tests/node/` and `src/sizing/tests/node/`\n- **Browser Tests** - Component tests using Playwright browser in `tests/browser/` and `src/tests/browser/`\n  - Setup file: `src/tests/browser/setup.ts`\n- **Ladle Stories** - Component stories in `src/_stories/` for development and visual testing\n- **Testing Context** - `VirtuosoDataTableTestingContext` for controlled test environments\n\n### Test Writing Guidelines\n\n**Use exact assertions, not ranges.** Tests must verify precise expected values:\n\n- Use `toBe(9)` not `toBeGreaterThan(0)` or `toBeLessThan(10)`\n- Use `toHaveLength(3)` not `toHaveLength(expect.any(Number))`\n- Calculate expected values from test constants (e.g., `Math.ceil((CONTAINER_HEIGHT - HEADER_HEIGHT) / ROW_HEIGHT)`)\n\n**Every assertion must be derivable from the test setup.** If you set `COLUMN_COUNT = 6` and `CONTAINER_WIDTH = 300` with `COLUMN_WIDTH = 150`, then assert exactly 2 visible columns, not \"less than 6\".\n\n**Avoid false-positive-prone patterns:**\n\n- `toBeGreaterThan(0)` - passes even when value is wrong\n- `toBeLessThan(TOTAL)` - passes even when virtualization is broken\n- `not.toBe(previousValue)` without asserting the new expected value\n\n**Correct pattern:**\n\n```typescript\nconst visibleColumns = Math.floor(CONTAINER_WIDTH / COLUMN_WIDTH)\nexpect(cells.length).toBe(visibleColumns)\n```\n\n### API Surface\n\nThe component is controlled through the reactive engine rather than a component ref API:\n\n- `engineRef` / `engineId` identify the table instance\n- `scrollToRow$` and `scrollIntoView$` handle programmatic scrolling\n- `scrollLocation$` exposes current scroll position information\n- `cancelSmoothScroll$` cancels the current smooth scroll animation\n\n### Key Design Patterns\n\n1. **Reactive State Management** - All state changes flow through the reactive engine reactive system\n2. **Declarative Data with Reactive Control** - Data is passed declaratively via prop; table operations use engine streams\n3. **Performance Optimization** - Only renders visible items with efficient size caching\n4. **Cross-platform Compatibility** - Handles browser-specific scroll behavior differences\n5. **Internal State Ownership** - The table manages its own state internally. External code does not lift state out of the table and pass it back in as props. Instead, the table's internal state is accessible and controllable through an imperative \"remote control\" pattern (e.g., ref methods, command channels). This avoids the lifted-state antipattern where the developer extracts table state, transforms it, and feeds it back, which creates extra render cycles and race conditions.\n\n## Research Guidelines\n\n- **Must not use articles, docs, or source code from** TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid.\n- **Must not reference external libraries** like TanStack Table, TanStack Virtual, AG Grid, MUI DataGrid, or similar - their architecture is irrelevant to this codebase\n- Focus exclusively on understanding and extending the existing patterns in this repository\n\n<!-- File: packages/data-table/docs/1.installation/01.shadcn.md (Tokens: ~1344 | Category: anthropic-skill) -->\n---\ntitle: Install With shadcn/ui\ndescription: Add the styled Virtuoso data table wrapper from the GitHub shadcn registry and start with a polished default table.\nsidebar:\n  label: Shadcn Install\n---\n\nThe shadcn registry copies a `DataTable` wrapper into `@/components/ui/data-table`. The wrapper re-exports the headless engine from `@virtuoso.dev/data-table` and ships pre-styled `DataTable`, `DataTableColumn`, `DataTableColumnHeader`, and `DataTableCell`. You edit the wrapper; the headless package stays installed as a dependency.\n\n## Prerequisites\n\n- A React application with TypeScript\n- Tailwind CSS configured\n- shadcn/ui initialized in the project\n- Path aliases resolvable from the root config shadcn reads. If a project uses a solution-style root `tsconfig.json` that only contains `references`, mirror the `@/*` path mapping there or shadcn may create a literal `@/components/...` directory instead of writing into `src/components/...`.\n\n## Add the registry component\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table\n```\n\nThe command writes the wrapper to `@/components/ui/data-table` and imports `@virtuoso.dev/data-table/styles.css` (structural layout defaults). No extra CSS setup is needed.\n\nFeature-specific wrapper components use the same GitHub registry address. For example, add the resize handle when you enable column resizing:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nAdd the sort header button when sortable column headers should dispatch model actions:\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button\n```\n\nThe hosted registry URL still works if you prefer the existing virtuoso.dev registry endpoint:\n\n```bash\nnpx shadcn@latest add https://virtuoso.dev/r/data-table.json\n```\n\n## Icon library\n\nThe wrapper uses [`lucide-react`](https://lucide.dev/) for the loading spinner, error icon, drag grip, and sort button icons. The `npx shadcn add` command installs `lucide-react` as a dependency on first run.\n\nshadcn's `iconLibrary` field in `components.json` only rewrites icon imports for the canonical shadcn registry — it does not touch components from third-party registries like ours. If your project uses a different icon set (`@radix-ui/react-icons`, `phosphor-react`, etc.) and you want the table icons to match, open the installed files under `@/components/ui/data-table` and swap the lucide imports for your library's equivalents. The icons appear in:\n\n- `index.tsx` — `Loader2` (spinner), `AlertCircle` (error state)\n- `column-reorder/reorder-grip.tsx` — `GripVertical` (drag handle)\n- `column-sort/sort-header-button.tsx` — `ArrowUpDown`, `ArrowUp`, `ArrowDown` (sort state)\n\n## Basic styled table\n\n```tsx live wide file=App.tsx\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\n\nimport { products } from './data'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: products })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 360 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport interface Product {\n  id: string\n  name: string\n  category: 'Audio' | 'Office' | 'Peripherals'\n  price: number\n  stock: number\n}\n\nexport const products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319, stock: 9 },\n]\n```\n\n## Next steps\n\nThe wrapper picks up your shadcn theme automatically (border radius, color tokens, typography). Edit `@/components/ui/data-table` directly for custom row hover states, sticky backgrounds, or denser spacing.\n\nThe two required setup steps after installation are [data model](/data-table/data-model/) and\n[columns](/data-table/columns/). For an inventory of what `npx shadcn add` actually wrote into\nyour project and how to edit it, see [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/);\nfor per-instance visual changes, see [Styling](/data-table/customization/styling/).\n\nRemote models, sticky columns, and instance control all import from `@virtuoso.dev/data-table` directly while the wrapper continues to provide the styled `DataTable`.\n\n<!-- File: packages/data-table/docs/1.installation/02.headless.md (Tokens: ~963 | Category: anthropic-skill) -->\n---\ntitle: Install The Headless Package\ndescription: Use the headless Virtuoso data table package directly when you need full control over styling and composition.\nsidebar:\n  label: Headless Install\n---\n\nThe headless package gives you `VirtuosoDataTable`, `Column`, `ColumnHeader`, and `Cell` without any preset styling. Pick it when you already have a design system, use non-Tailwind styling, or need fully custom markup around the table.\n\n## Install the package\n\n```bash\npnpm add @virtuoso.dev/data-table\n```\n\n`npm`, `yarn`, and `bun` work too.\n\n## Import structural styles\n\nThe package ships layout defaults (column sizing, box model) in a `@layer virtuoso` block. Import the stylesheet once in your app's CSS or entry point:\n\n```css\n@import '@virtuoso.dev/data-table/styles.css';\n```\n\n```typescript\nimport '@virtuoso.dev/data-table/styles.css'\n```\n\nThe `@layer virtuoso` wrapping puts these rules at the lowest cascade priority, so any plain CSS or Tailwind utility class overrides them.\n\n## Minimal headless example\n\n```tsx live wide file=App.tsx\nimport { Cell, Column, ColumnHeader, VirtuosoDataTable, localModel } from '@virtuoso.dev/data-table'\n\nimport { rows } from './data'\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <VirtuosoDataTable\n      model={model}\n      style={{\n        height: 360,\n        border: '1px solid var(--border)',\n        borderRadius: 16,\n        overflow: 'hidden',\n      }}\n    >\n      <Column field=\"name\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Product</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"category\">\n        <ColumnHeader>\n          {() => <div style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center' }}>Category</div>}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div style={{ padding: '0 12px', height: 44, display: 'flex', alignItems: 'center' }}>{String(cellValue)}</div>\n          )}\n        </Cell>\n      </Column>\n      <Column field=\"price\">\n        <ColumnHeader>\n          {() => (\n            <div\n              style={{ padding: '0 12px', fontWeight: 600, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}\n            >\n              Price\n            </div>\n          )}\n        </ColumnHeader>\n        <Cell>\n          {({ cellValue }) => (\n            <div\n              style={{\n                padding: '0 12px',\n                height: 44,\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'flex-end',\n                fontVariantNumeric: 'tabular-nums',\n              }}\n            >\n              {currency.format(Number(cellValue))}\n            </div>\n          )}\n        </Cell>\n      </Column>\n    </VirtuosoDataTable>\n  )\n}\n```\n\n```ts live file=data.ts\nexport const rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169 },\n  { id: 'SKU-004', name: 'Noise-canceling Headset', category: 'Audio', price: 319 },\n]\n```\n\n## Next steps\n\nThe headless package exposes the full surface area: local and remote data models, instance control, scroll control, and testing utilities. Continue with [data model](/data-table/data-model/) and [columns](/data-table/columns/).\n\n<!-- File: packages/data-table/docs/2.data-model/01.local-data-model.md (Tokens: ~3273 | Category: anthropic-skill) -->\n---\ntitle: Local Data Model\ndescription: Use localModel for in-memory rows, derived views, persistence, and grouped data.\nsidebar:\n  label: Local Data Model\n---\n\nWhen the rows are already in the browser — a hardcoded array, React state, a client-side collection mutated by user actions — `localModel()` is the model that wraps them. It holds the array in memory and exposes actions that mutate or derive the rows the table displays.\n\n```typescript\nconst model = localModel({ data: products })\n```\n\nPass the model to the table:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  {/* columns */}\n</DataTable>\n```\n\n## Where to keep the model\n\nThe model holds reactive state — engine subscriptions, the pipeline's stage cache, persisted action payloads. Recreating it discards that state, so the model instance must outlive renders. Two patterns cover almost every case:\n\n**Module scope** — when the dataset is static and the table is a singleton on the page. This is the simplest possible form and matches how most examples in these docs are written.\n\n```tsx\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel({ data: products })\n\nexport function ProductsTable() {\n  return <DataTable model={model}>{/* columns */}</DataTable>\n}\n```\n\nThe model is shared by every render and every mount of `ProductsTable`. Filter and sort state survives navigations away from the page — usually desirable; occasionally not. Initial config can't depend on props because there's no component scope yet.\n\n**`useState` with lazy init** — inside the component, for everything else: multiple table instances on the same page, initial data sourced from props, dynamic mount/unmount.\n\n```tsx\nconst [model] = useState(() => localModel<Product>({ data: initialProducts }))\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Reach for this whenever the model can't live in module scope.\n\nAvoid `useMemo(() => localModel(...), [])` for this purpose. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which would silently reset the model's subscriptions and state. `useState` is the only hook that guarantees retention.\n\n## Pipelines\n\nSource rows rarely match what a table should actually display. A search box trims them down, a header click reorders them, a toolbar groups them by category. A **pipeline** is the ordered chain of stages that derives the rendered rows from the source array — each stage takes the previous stage's output, transforms it, and hands it to the next one.\n\nTwo properties keep the pipeline predictable as actions land:\n\n- The order is **declared once**, so every action lands in a predictable place instead of racing against the others.\n- Each stage's output is **cached**, so an unrelated stage doesn't rerun when only its successor changes.\n\nDeclare the stage order on the model, then attach each handler to the stage it belongs to:\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter', 'sort'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      handler: ({ data, payload }) => data.filter((row) => row.category === payload),\n    },\n    sort: {\n      stage: 'sort',\n      handler: ({ data }) => data.toSorted((a, b) => a.name.localeCompare(b.name)),\n    },\n  },\n})\n```\n\nStage names are open labels — `filter`, `sort`, and `group` are the conventional ones, but you can split work across whatever stages your table needs (`enrich`, `paginate`, `dedupe`). Inside each handler, `data` is whatever the previous stage produced: filter sees the source rows, sort sees the filtered rows, a later group stage would see the sorted ones.\n\nWhen the `filter` payload changes, the filter stage and the later sort stage both rerun. When only `sort` changes, the cached filter result is reused — the difference matters once filtering or grouping touches thousands of rows.\n\n## Source mutators\n\nAn action declared without a `stage` mutates the backing rows. Source mutators invalidate the full pipeline because every derived stage depends on the new source.\n\n```typescript\nconst model = localModel({\n  data: products,\n  actions: {\n    renameProduct: {\n      handler: ({ source, payload }) => source.map((row) => (row.id === payload.id ? { ...row, name: payload.name } : row)),\n    },\n  },\n})\n\nmodel.send({ action: 'renameProduct', payload: { id: 'SKU-001', name: 'Desk Pro' } })\n```\n\n`model.send({ action, payload })` is how buttons, inputs, effects, and table controls dispatch actions. The action name picks the handler; the payload is whatever your handler expects.\n\n## React state updates\n\nWhen rows arrive from React state, a websocket, or a parent workflow, push the next row set into the model instead of recreating it — see [Where to keep the model](#where-to-keep-the-model) for why the instance must stay stable.\n\n```tsx\nconst [model] = React.useState(() => localModel<Product>({ data: initialProducts }))\n\nReact.useEffect(() => {\n  model.setData?.(products)\n}, [model, products])\n```\n\nFor grouped rows, `model.setData(nextRows, nextGroups)` updates rows and markers together.\n\nIf the row schema changes intentionally, remount the table with a new `key` so runtime columns can discover the new first result. See [Runtime Columns](/data-table/columns/runtime-columns/#rediscovering-columns).\n\n### Updating rows in place\n\n`setData` resets the table's size tracking along with the rows — every row is measured again, which briefly unmounts and remounts visible cells. When a refresh only changes row values — a live price tick, a status flip — and the row set is otherwise identical, `model.updateData(nextRows)` skips that reset:\n\n```tsx\nReact.useEffect(() => {\n  model.updateData?.(products)\n}, [model, products])\n```\n\n`updateData` requires the caller to guarantee a strict contract between `nextRows` and the current rows:\n\n- the row count is unchanged\n- each index still holds the same logical row — no insertions, deletions, or reorders\n- `computeRowKey` returns the same key for each row before and after the update\n- group markers are unaffected — `updateData` never takes a `groups` argument, since group placement is index/level metadata a value-only update can't legitimately change\n- row values that do not determine the row key may change freely\n\nCached sizes are retained as estimates for the new values and corrected by `ResizeObserver` for any row that's actually mounted. If the row count doesn't match, the table falls back to a full reset and logs a dev warning.\n\nA pipeline stage that sorts or filters on a field the update just changed can reorder rows at the same indices, which breaks the \"same logical row per index\" contract and misattributes cached heights to the wrong row. `updateData` on a model with a `pipeline` logs a dev warning for this reason — use `setData` there instead.\n\nA custom model (built directly on the model protocol rather than `localModel`) opts into the same behavior by returning `operation: 'update'` on the `DataResult` it emits.\n\n## Grouping\n\nWhen rows naturally cluster — sales by region, products by category, employees by department — group headers belong between the rows they introduce. The library keeps those headers inside the same flat `data` array rather than nesting rows under each header, so the pipeline, sizing, and virtualization treat them as ordinary rows. A separate `groups` array tells the table which entries are headers and how deeply they nest.\n\n### Static groups\n\nWhen the grouping is computed up front, pass pre-grouped rows and markers to `localModel`:\n\n```tsx\nconst rows = [\n  { label: 'Office' },\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  { label: 'Peripherals' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals' },\n]\n\nconst groups = [\n  { index: 0, level: 0 },\n  { index: 3, level: 0 },\n]\n\nconst [model] = useState(() => localModel({ data: rows, groups }))\n```\n\n`index` points at the group row in `data`. `level` is the nesting depth — `0` for top-level headers, `1` for headers nested under them, and so on.\n\n### Multiple levels\n\nHigher `level` values render deeper and stick under their parent when the user scrolls. A two-level `region → country → city` table declares each region marker at `level: 0` and each country marker at `level: 1`:\n\n```typescript\nconst data = [\n  { groupName: 'North America' },\n  { groupName: 'USA' },\n  { name: 'New York', population: 8_336_000 },\n  { name: 'Los Angeles', population: 3_898_000 },\n  { groupName: 'Canada' },\n  { name: 'Toronto', population: 2_794_000 },\n  { groupName: 'Europe' },\n  { groupName: 'UK' },\n  { name: 'London', population: 8_982_000 },\n]\n\nconst groups = [\n  { index: 0, level: 0 }, // North America\n  { index: 1, level: 1 }, // USA\n  { index: 4, level: 1 }, // Canada\n  { index: 6, level: 0 }, // Europe\n  { index: 7, level: 1 }, // UK\n]\n```\n\nThe table treats `level` as a depth hint, not a tree. There's no `parent` field — a `level: 1` header is rendered as a child of whichever `level: 0` header most recently preceded it in `data`, and sticky headers reserve a row per active level on screen. Skipping levels or reordering markers is allowed; the table still renders every row in the order you provided.\n\n### Computing groups in a pipeline\n\nWhen the grouping dimension is interactive — \"group by category\" toggling to \"group by status\" — keep the source rows flat and compute markers inside a `group` pipeline stage. The handler returns `{ data, groups }` instead of a bare array, and the pipeline updates rows and markers atomically:\n\n```tsx\nfunction groupBy<T extends Record<string, unknown>>(rows: T[], field: keyof T | null) {\n  if (!field) return { data: rows, groups: [] as { index: number; level: number }[] }\n\n  const buckets = new Map<string, T[]>()\n  for (const row of rows) {\n    const key = String(row[field])\n    let bucket = buckets.get(key)\n    if (!bucket) buckets.set(key, (bucket = []))\n    bucket.push(row)\n  }\n\n  const data: (T | { label: string })[] = []\n  const groups: { index: number; level: number }[] = []\n  for (const [key, bucketRows] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${String(field)}: ${key} (${bucketRows.length})` })\n    data.push(...bucketRows)\n  }\n  return { data, groups }\n}\n\nconst [model] = useState(() =>\n  localModel({\n    data: products,\n    pipeline: ['filter', 'group'],\n    actions: {\n      filter: {\n        stage: 'filter',\n        handler: ({ data, payload }) => (payload ? (data as Product[]).filter((row) => row.category === payload) : data),\n      },\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupBy(data as Product[], payload as keyof Product | null),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'group', payload: 'category' })\n```\n\nStage order matters here. With `['filter', 'group']`, the filter narrows the source rows and the group stage buckets only what survived — the natural pairing. Putting `group` before `filter` would force the filter handler to special-case header rows, which is rarely worth the complexity.\n\nA handler that returns `{ data, groups }` updates both. Later stages that return a bare array reuse the most recent `groups` from earlier in the pipeline, so a `sort` after `group` doesn't need to know about markers as long as it preserves their indexes (typically by sorting within each bucket).\n\nTo push pre-grouped rows from outside the pipeline entirely — for example after a parent fetch — call `model.setData(nextRows, nextGroups)`.\n\n### Connecting the UI\n\n`<GroupHeaderCell>` renders the entries marked as headers. It receives the original row and the marker's `level`, so styling can branch on nesting depth:\n\n```tsx\n<DataTable model={model} style={{ height: 360 }}>\n  <GroupHeaderCell>\n    {({ row, level }) => (\n      <div\n        style={{\n          paddingLeft: 12 + level * 16,\n          fontWeight: level === 0 ? 700 : 500,\n          background: level === 0 ? '#e8e8e8' : '#f4f4f4',\n        }}\n      >\n        {(row.data as { groupName: string }).groupName}\n      </div>\n    )}\n  </GroupHeaderCell>\n  {/* columns */}\n</DataTable>\n```\n\n`row.data` is whatever object you pushed at that index in `data` — the shape is yours to define, the cell just renders what you return. Group rows are virtualized like every other row, so keep the rendered markup light.\n\nFor the full render-side reference — remote-paginated grouping, sticky-header behavior, and a runnable end-to-end example — see [Grouped Rows](/data-table/grouped-rows/).\n\n## Action persistence\n\nPersist action payloads — search terms, filter values, sort choice, grouping mode — so user intent survives reloads. Row data is never persisted.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: true,\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPass an object instead of `true` to set a custom storage key or define empty-state handling.\n\n```typescript\nconst model = localModel({\n  data: products,\n  pipeline: ['filter'],\n  actions: {\n    filter: {\n      stage: 'filter',\n      persistence: {\n        key: 'categoryFilter',\n        isEmpty: (value) => !value,\n      },\n      handler: ({ data, payload }) => (payload ? data.filter((row) => row.category === payload) : data),\n    },\n  },\n})\n```\n\nPair this with `modelStatePersistenceAdapter()` from [State Persistence](/data-table/state-persistence/) to save model action state alongside column width, order, and visibility.\n\n`isEmpty` skips a payload from saved state when it represents \"no active filter\" — empty strings, `\"all\"` selections, `null` values.\n\n<!-- File: packages/data-table/docs/2.data-model/02.remote-data-model.md (Tokens: ~2473 | Category: anthropic-skill) -->\n---\ntitle: Remote Data Model\ndescription: Use remoteModel when rows are fetched as request params, cursors, or rendered ranges change.\nsidebar:\n  label: Remote Data Model\n---\n\nWhen a table's rows live behind an API and there are too many to load up front — paginated catalogs, search results, infinite feeds — `remoteModel()` is the model that handles the back-and-forth with your backend.\n\nYou give it a `fetch` function that loads a slice of rows. The model calls that function at the right moments (the user scrolled into rows that haven't loaded yet, a search changed, an action fired), feeds the results back into the table, and cancels requests that have been overtaken by newer ones.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n    placeholder: loadingProduct,\n  })\n)\n```\n\n## Where to keep the model\n\nThe model carries live state across renders — requests still in flight, the controllers that can cancel them, persisted action payloads, and the bookkeeping that decides what happens when actions overlap. Recreate the model on every render and that state is thrown away: pending fetches get aborted mid-flight and the next render starts the cycle over with fresh ones. The instance has to outlive renders.\n\nFor most remote tables, **`useState` with lazy initialization** is the right place:\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel<Product, Query>({\n    fetch: fetchProducts,\n    initialParams: { category, search },\n    onViewportChange: defaultOffsetViewportHandler,\n    pageSize: 50,\n  })\n)\n```\n\n`useState`'s lazy initializer runs exactly once per mount, and React guarantees the value survives every subsequent render. Use this whenever initial params come from URL state, props, or anything else the surrounding component has access to but a module does not.\n\n**Module scope** works for genuinely static config — a fixed endpoint, no per-route params, one table per page:\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { category: 'all', search: '' },\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nBe aware that every mount of every consumer shares the same instance, and therefore the same fetch lifecycle and persisted action state. That can be a feature (cached results survive navigation) or a footgun (two routes appearing to share filter state).\n\nAvoid `useMemo(() => remoteModel(...), [])`. `useMemo` is contractually a performance hint — React reserves the right to discard the memoized value, which for a remote model means cancelling pending fetches and resetting concurrency state silently. `useState` is the only hook with a retention guarantee.\n\n> The remaining examples on this page write `const model = remoteModel({...})` for brevity, focusing each snippet on the option it's introducing. In real code, wrap the call in `useState(() => ...)` or place it at module scope using the rules above.\n\n## Pagination and infinite scrolling\n\n`remoteModel` ships two built-in patterns for deciding what to fetch as the user scrolls. The right choice depends on how your backend serves data and what kind of scrolling experience the table should deliver.\n\n### Pagination with a known total\n\nReach for **offset mode** when the backend accepts `(offset, limit)` slices and can tell you how many rows exist in total. The table renders placeholder rows for everything that hasn't been fetched, so the scrollbar reflects the full dataset, the user can jump anywhere via the scrollbar or keyboard, and each slice is requested as it enters the viewport.\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: defaultOffsetViewportHandler,\n  pageSize: 50,\n})\n```\n\nThis is what most search-and-filter UIs want: product catalogs, admin tables, anything backed by a SQL `LIMIT/OFFSET` or an Elasticsearch `from/size` query.\n\n### Infinite scrolling\n\nReach for **append mode** when the backend is cursor-based, the total isn't known up front, or new rows arrive over time — social feeds, message timelines, activity logs.\n\n```typescript\nconst model = remoteModel({\n  mode: 'append',\n  fetch: fetchFeed,\n  onViewportChange: defaultAppendViewportHandler,\n  pageSize: 30,\n})\n```\n\nRows accumulate as the user approaches the end of what's already loaded. The scrollbar only covers the loaded portion because the table can't know how many rows are out there; the loading state shows a footer at the bottom while the next page is fetched.\n\n### Custom fetch windows\n\nWhen neither default matches — the backend uses prefetch hints, variable page sizes, or needs requests coalesced into fewer round-trips — write a custom `onViewportChange`. Given the rendered range and the page size, return one or more slices for the model to fetch:\n\n```typescript\nconst model = remoteModel({\n  fetch: fetchProducts,\n  onViewportChange: ({ endIndex, pageSize, totalCount }) => {\n    const nextOffset = Math.floor((endIndex + 1) / pageSize) * pageSize\n\n    if (nextOffset < totalCount) {\n      return {\n        fetch: [{ offset: nextOffset, limit: Math.min(pageSize, totalCount - nextOffset) }],\n      }\n    }\n  },\n  pageSize: 50,\n})\n```\n\nOffset-mode handlers return `{ fetch: [{ offset, limit }, ...] }`; append-mode handlers return `{ loadMore: true }`.\n\n## Fetch results\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&search=${params.search}`, {\n    signal,\n  })\n\n  return response.json()\n}\n```\n\nOffset mode expects `{ rows, totalCount }`. Add `groups` when the backend returns a flattened grouped result:\n\n```typescript\nreturn {\n  rows: result.rows,\n  groups: result.groups,\n  totalCount: result.totalCount,\n}\n```\n\nFor backends that define the row schema, return schema metadata next to the rows or keep a separate field list in application state — see [Generating Columns at Runtime](/data-table/columns/runtime-columns/).\n\n`pageSize` controls the slice size. Big enough that ordinary scrolling doesn't request on every small move, small enough that refreshes stay responsive.\n\n`placeholder` is the row shape used while first-page rows are still loading. Include every field your column renderers read.\n\n## Remote actions\n\nRemote actions translate UI intent into the next request's params. URL query parameters are the common case; the same pattern works for cursor filters, request bodies, search objects, or anything else `fetch` reads from `params`.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    initialParams: { category: 'all', search: '' },\n    actions: {\n      search: {\n        strategy: 'supersede',\n        persistence: true,\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      category: {\n        persistence: { key: 'categoryFilter' },\n        handler: ({ params, payload }) => ({ ...params, category: String(payload) }),\n      },\n    },\n  })\n)\n\nmodel.send({ action: 'search', payload: 'desk' })\n```\n\nReturning new params triggers a refresh. With `persistence: true`, the payload of each action — the search string, the selected category — is saved alongside the rest of the table's persisted state and replayed through the handler the next time the table loads, so users come back to the same filtered view. Pass `capture` and `restore` instead of `true` when you need a custom serialized shape.\n\nFor stateful controls such as sort, filter, group, or search, prefer the model action state as the source of truth. Seed default active controls with `initialActions`, dispatch changes through `model.send()` or `dispatchModelAction$`, and read `modelActionState$` to paint active buttons. Do not mirror sort/filter payloads in React state just to keep controls highlighted; if a control must be controlled by React, explicitly restore that local state when using `modelStatePersistenceAdapter()`.\n\nFor a complete column-header sort control that sends `{ field, direction }` to a remote model, see [Remote Column Sorting](/data-table/examples/remote-column-sorting/).\n\n## Concurrency and cancellation\n\n`strategy` controls what happens when an action fires while a fetch is still in flight.\n\n```tsx\nconst [model] = useState(() =>\n  remoteModel({\n    fetch: fetchProducts,\n    actions: {\n      search: {\n        strategy: 'supersede',\n        handler: ({ params, payload }) => ({ ...params, search: String(payload) }),\n      },\n      exportRows: {\n        strategy: 'queue',\n        handler: ({ params, payload }) => ({ ...params, exportId: String(payload) }),\n      },\n    },\n  })\n)\n```\n\n- `supersede` — cancels the active request for the same action and uses the latest payload. Right for search boxes and filters.\n- `queue` — runs every request in order. Right for ordered workflows where no request can be dropped.\n- `deduplicate` — collapses identical concurrent requests. Right for retry buttons and repeated load commands.\n\nEvery `fetch` call receives an `AbortSignal`. Pass it to `fetch()` or your client library so superseded refreshes, viewport requests, and disconnects cancel cleanly.\n\n```typescript\nasync function fetchProducts({ offset, limit, params, signal }) {\n  const response = await fetch(`/api/products?offset=${offset}&limit=${limit}&q=${params.search}`, {\n    signal,\n  })\n  return response.json()\n}\n```\n\n`onError` fires for failed fetches. Aborted requests publish a loading cancellation event instead.\n\n## Loading state\n\nWhile a fetch is in flight the model emits loading events that the table turns into visible UI — a placeholder for the first load, an overlay for refreshes, and a footer for append-mode pagination. The shadcn wrapper renders sensible defaults for all three slots, so most tables don't need to handle loading state by hand. Reach for the events directly only when something outside the table — a global progress bar, a status badge, a logging hook — needs to react to the same lifecycle.\n\nThree phases drive the visible slots:\n\n- `initial` — first unresolved dataset; rendered as `LoadingPlaceholder`.\n- `refresh` — request params changed while old rows stay visible; rendered as `LoadingOverlay`.\n- `end` — append mode is fetching more rows below the current data; rendered as `LoadingFooter`.\n\nOffset viewport fetches show up as placeholder rows rather than table-level loading and don't update `loadingState$`. Manual `loadRange` calls publish `viewport` events for subscribers, but the loading slots ignore them.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nFor external controls, logging, or diagnostics, subscribe to the model's event channel:\n\n```typescript\nconst unsubscribe = model.subscribe((message) => {\n  if (message.type === 'event' && message.payload?.kind === 'loading') {\n    console.log(message.payload.reason, message.payload.phase)\n  }\n})\n```\n\n`loadingState$` (read via `useVirtuosoLoadingState()` or directly inside the table) is updated from the same `initial`, `refresh`, and `end` events.\n\n<!-- File: packages/data-table/docs/2.data-model/03.row-keys.md (Tokens: ~676 | Category: anthropic-skill) -->\n---\ntitle: Row Keys\ndescription: Give the table stable row keys so React reconciles rows correctly when the model reorders them.\nsidebar:\n  label: Row Keys\n---\n\n`computeRowKey` returns the React key for a row — the value React uses to decide whether the row in this render is the same logical row as one from the previous render. Provide a stable key (typically derived from `data.id`), and React reconciles the row in place even when its index changes under a sort, filter, group, prepend, or append.\n\n```tsx\n<DataTable computeRowKey={({ data }) => data.id} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## Why row keys matter\n\nWithout `computeRowKey`, the key falls back to the virtual row position. That works for tables whose rows never move, but as soon as the model reorders — a pipeline `filter` stage runs, a `sort` stage reshuffles the result, a new page is appended, a remote refetch returns rows in a new order — every row whose index changed remounts.\n\nThe symptom is usually a user-visible glitch: a search input loses focus mid-keystroke because the row it lives in moved one position, an expanded detail row collapses because its parent shifted, a chart inside a cell redraws because the cell got a new React identity. The data is still correct; the React identity broke. `computeRowKey` is what stops that from happening.\n\nThis is a React reconciliation rule, not a table-specific concern. It applies to any data table where rows can move, regardless of which other table features you use.\n\n## Grouped data\n\nGrouped data mixes data rows and group rows in the same flat array (see [Grouping](/data-table/data-model/local-data-model/#grouping)). The function receives both shapes, so return a key for each:\n\n```tsx\n<DataTable computeRowKey={({ data, index }) => ('id' in data ? data.id : `group:${data.label}:${index}`)} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nGroup rows rarely carry a stable ID from your data, so a composite key built from a label plus index is fine — the table doesn't try to reconcile group rows across regrouping anyway. The important thing is that data rows keep their identity through reorder; group rows are allowed to remount when the grouping dimension changes.\n\n## Function signature\n\n`computeRowKey` receives an object with three fields:\n\n- `data` — the row at this index (the data row or the group row)\n- `index` — the row's virtual position in the table\n- `context` — the table's [ambient context](/data-table/customization/ambient-context/), useful for scoping keys across tenants, saved views, or other instances that might share row IDs\n\n```tsx\n<DataTable computeRowKey={({ data, context }) => `${context.tenantId}:${data.id}`} context={{ tenantId: 'acme' }} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nCross-scope key prefixing matters when one component instance is reused for different tenants or views — without it, React would see the same key for what's actually a different row and skip reconciliation it shouldn't have skipped.\n\n<!-- File: packages/data-table/docs/2.data-model/index.md (Tokens: ~598 | Category: anthropic-skill) -->\n---\ntitle: Choosing a Data Model\ndescription: Give the table a local or remote model before adding table features.\nsidebar:\n  label: Choosing a Model\n---\n\nThe model owns the rows; columns own the rendering. Pick the model that matches where rows come from:\n\n- [`localModel()`](/data-table/data-model/local-data-model/) — rows already in the browser, with filter, sort, group, and edit actions deriving the displayed rows.\n- [`remoteModel()`](/data-table/data-model/remote-data-model/) — rows fetched from an API as request params, cursors, or the rendered range change.\n\nBoth models can persist action state (filter values, sort choice, grouping mode) through opt-in adapters. Row data itself is never persisted.\n\n## Basic local model\n\nUse a local model for in-memory rows. Keep the model stable and pass it to `DataTable` with\n`model={model}`.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>Category</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Where to go next\n\n- [Local Data Model](/data-table/data-model/local-data-model/) — pipelines, source updates, persistence, grouping (single- and multi-level).\n- [Remote Data Model](/data-table/data-model/remote-data-model/) — pagination vs. infinite scrolling, actions, cancellation, grouped rows, loading UI.\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n\n<!-- File: packages/data-table/docs/3.columns/01.defining-columns.md (Tokens: ~1113 | Category: anthropic-skill) -->\n---\ntitle: Defining Columns\ndescription: Declare the fields, order, defaults, and basic behavior of a data-table column.\nsidebar:\n  label: Defining Columns\n---\n\n`DataTableColumn` declares a column. For data-backed columns, pass `field`; it reads `row.data[field]`, uses that value as `cellValue`, and uses the field name as the stable column identity for persistence, reordering, resizing, and visibility.\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>Name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor a row `{ name: 'Standing Desk' }`, the cell renderer receives `'Standing Desk'` as `cellValue`. Omitting `DataTableCell` falls back to the field value; omitting `DataTableColumnHeader` on a field-backed column falls back to the field name. In app UIs, prefer explicit headers so you control capitalization, wording, and accessibility.\n\nDisplay-only columns do not need matching row fields. Give them an explicit `id` instead. The id becomes the stable column identity, and `cellValue` is `undefined` unless you provide an `accessor`.\n\n```tsx\n<DataTableColumn id=\"actions\" sticky=\"right\">\n  <DataTableColumnHeader>Actions</DataTableColumnHeader>\n  <DataTableCell>{({ row }) => <ActionsMenu row={row.data} />}</DataTableCell>\n</DataTableColumn>\n```\n\n`id` is not a visible label. For action, selection, expansion, status-icon, and other display-only columns, always provide a `DataTableColumnHeader`. Use visible text such as `Actions` by default; use `sr-only` text only when the product intentionally wants a headerless icon column.\n\nComputed columns also use an explicit `id`, plus an `accessor` when you want the table to provide a derived `cellValue`.\n\n```tsx\n<DataTableColumn id=\"fullName\" accessor={(row) => `${row.firstName} ${row.lastName}`}>\n  <DataTableColumnHeader>Full name</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Minimal columns\n\nAdd an explicit header or cell when you need formatting; leave both off when the field name and stringified value are enough.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 260 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\" />\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Declaration order\n\nColumns render in declaration order. Reordering and persistence change the runtime order; the declaration order stays as the baseline used after a reset.\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"name\" />\n  <DataTableColumn field=\"category\" />\n  <DataTableColumn field=\"price\" />\n</DataTable>\n```\n\n## Visibility and sticky\n\n`visible={false}` hides a column by default. Runtime visibility lives in the [Column Visibility](/data-table/columns/column-visibility/) page.\n\n```tsx\n<DataTableColumn field=\"internalSku\" visible={false} />\n```\n\n`sticky=\"left\"` or `sticky=\"right\"` pins a column for row identity, IDs, or actions that should stay visible during horizontal scrolling — see [Sticky Columns](/data-table/columns/sticky-columns/).\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\" />\n<DataTableColumn id=\"actions\" sticky=\"right\" />\n```\n\n## Generated columns\n\nFor dynamic field lists, map them to `DataTableColumn` declarations. Keep the field order deterministic — the field values become the column identities.\n\n```tsx\nconst fields = ['name', 'category', 'stock']\n\n;<DataTable model={model}>\n  {fields.map((field) => (\n    <DataTableColumn field={field} key={field}>\n      <DataTableColumnHeader>{field}</DataTableColumnHeader>\n      <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n    </DataTableColumn>\n  ))}\n</DataTable>\n```\n\nWhen the field list itself is discovered from the first data result, see [Runtime Columns](/data-table/columns/runtime-columns/).\n\n<!-- File: packages/data-table/docs/3.columns/02.cell-and-header-renderers.md (Tokens: ~2511 | Category: anthropic-skill) -->\n---\ntitle: Formatting Cells and Headers\ndescription: Use render params to format cells and headers from column and row state.\nsidebar:\n  label: Formatting Cells\n---\n\nMost columns need formatting that goes beyond stringifying a field — currency values, status badges, stock-level colors, multi-line product names. `DataTableCell` and `DataTableColumnHeader` are where that formatting lives. Each one accepts a render function that the table calls when it paints a body cell or header; only cells inside (or close to) the viewport actually run, so the JSX stays a static, declarative description of the structure while the work scales to large tables.\n\n## Style the correct header surface\n\n`DataTableCell` accepts a `className` prop alongside its render function. The class lands on the table's own cell wrapper, so use it for alignment, font weight, text color, `tabular-nums`, and sticky-specific colors instead of wrapping the render output in another `<div>`.\n\n```tsx\n<DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n```\n\nHeaders have two surfaces because a grow column has both a base width and a rendered width. The table observes the intrinsic header to determine the base width, then assigns spare space to growing columns. If the observed element also stretches across that spare space, its rendered width becomes the next base measurement and the column cannot shrink when the table narrows. Keeping measurement and presentation on separate elements prevents that feedback loop.\n\n`className` styles the intrinsic header content that the table measures. Use it for width, minimum width, padding, typography, and alignment within the base width. `containerProps` styles the outer container after the table assigns the rendered column width. Use it for paint and interaction that must cover the full column, such as a background, hover state, outline, box shadow, cursor, ARIA attribute, or event handler.\n\n```tsx\n<DataTableColumnHeader\n  className=\"min-w-64\"\n  containerProps={{\n    className: 'cursor-pointer hover:bg-muted/50',\n  }}\n>\n  Product\n</DataTableColumnHeader>\n```\n\nThe outer container's geometry belongs to the table. Do not put width, flex, margin, padding, border, display, or `justify-*` utilities on `containerProps`; these can clip the measured content or move edge-aligned header slots. Keep those layout styles on `className`, use `box-shadow` or `outline` for full-width separators, and use `HeaderEnd` for a control that must stay at the rendered column edge.\n\nEvents attached through `containerProps` receive bubbled events from header slots and nested controls. When a header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nKeep width utilities such as `w-*` and `min-w-*` on `DataTableColumnHeader.className`. A `w-full` or flex-growth class on this intrinsic surface resolves only within the measurement boundary; it does not cover the rendered track. Use `containerProps` for that visual result. Do not put width utilities on `DataTableCell`. Use `DataTableColumn grow={...}` when a column should absorb leftover horizontal space. A cell width can make the body content fight the measured grid track and overlap neighboring columns. For cell content that needs truncation or nested layout, keep the cell unconstrained and put `min-w-0` on an inner wrapper:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-64\">Name</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ row }) => <div className=\"min-w-0 truncate\">{row.data.name}</div>}</DataTableCell>\n</DataTableColumn>\n```\n\nEach visible cell is its own element; the table also column- and row-virtualizes, so an extra wrapper inside the render function multiplies by every rendered cell on every scroll. Save nested elements for styling that's only part of the cell content — a status badge, an inline icon, a secondary line of metadata.\n\nThe shadcn wrapper accepts plain text for static headers:\n\n```tsx\n<DataTableColumnHeader>Product</DataTableColumnHeader>\n```\n\nAnd a render function for headers that depend on table state:\n\n```tsx\n<DataTableColumnHeader>{({ columnState }) => (columnState.sticky ? 'Pinned product' : 'Product')}</DataTableColumnHeader>\n```\n\n## Cell values and rows\n\n`cellValue` is the selected field value or the result of the column `accessor`; `row.data` is the whole row. Reach for `row.data` whenever the cell depends on more than one field or renders UI that is not data-backed.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : `${row.data.stock} in stock`}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Render params\n\nThe render function receives a single object whose fields surface what you'd otherwise have to look up by hand. `cellValue` is the shortcut for `row.data[column.field]` on field columns, the result of `accessor(row.data)` on computed columns, and `undefined` on id-only display columns. `row.data` is there for cells that depend on more than one field. `column` and `columnKey` identify which column this invocation is rendering — useful when one extracted renderer drives multiple columns. `column.id` is the stable public identity when you provided one; `column.field` exists only for columns that read a row-data field. `columnState` lets the renderer branch on runtime column state (currently just `sticky`, so headers and cells can style differently when the column is pinned). `overlaidByScrollbar` flips to `true` when a native OS scrollbar overlaps the cell's edge — that's the table telling you trailing UI in this cell is unreachable, so move buttons or actions inboard while the flag is set. Header params have the same shape minus `cellValue` and `row` — a header has no row to render against.\n\n```typescript\ntype CellRenderParams = {\n  cellValue: unknown // row.data[column.field], accessor(row.data), or undefined\n  row: RowItem // the virtualized row\n  column: ColumnDefinition\n  columnKey: string // internal key for this column instance\n  columnState: ColumnState // currently: { sticky }\n  overlaidByScrollbar: boolean // true when a native scrollbar overlaps the cell edge\n}\n\ntype ColumnHeaderRenderParams = Omit<CellRenderParams, 'cellValue' | 'row'>\n```\n\n```tsx\n<DataTableColumnHeader>\n  {({ column, columnState }) => <span className={columnState.sticky ? 'font-semibold' : undefined}>{column.field}</span>}\n</DataTableColumnHeader>\n```\n\n## Extracting renderers\n\nInline render functions become unwieldy past a couple of lines, and they limit what you can do with the renderer itself. Pulling them out into named functions — or full components — buys three things at once: they appear by name in React DevTools (instead of as anonymous arrow functions), one renderer can drive several columns that share the same formatting, and you can wrap the renderer in `React.memo` so it skips re-renders when the row data hasn't changed — useful for expensive cells like rich text or chart sparklines.\n\nThe render-param types are exported and double as the props type for the component:\n\n```tsx\nimport type { CellRenderParams, ColumnHeaderRenderParams } from '@virtuoso.dev/data-table'\n\nfunction StockCell({ row }: CellRenderParams) {\n  return <span>{row.data.stock < 20 ? 'Low stock' : 'Healthy'}</span>\n}\n\nfunction FieldHeader({ column }: ColumnHeaderRenderParams) {\n  return <span>{column.field ?? column.id}</span>\n}\n```\n\nExtracted renderers are ordinary React components. They can call hooks, subscribe to React context, and compose with `React.memo` like any other component. One thing they _don't_ receive is the table's `context` prop — that channel exists for table infrastructure (empty-state, sticky wrappers, scroll element) and doesn't reach cell or header renderers. Close over screen-local state directly, or read it through React context if multiple columns need it.\n\nPass renderers as children of the cell or header:\n\n```tsx\n<DataTableColumn field=\"stock\">\n  <DataTableColumnHeader>{FieldHeader}</DataTableColumnHeader>\n  <DataTableCell>{StockCell}</DataTableCell>\n</DataTableColumn>\n```\n\n## Header slots\n\nA header can host additional UI alongside the label — sort buttons, filter menus, resize handles, drop indicators for reordering. Inline UI (buttons, menus) can go in the header's render function. Two positions don't fit there: a resize handle has to sit on the column boundary independent of label alignment, and a drop indicator has to span the entire header during a reorder drag. The table provides four named slots for these cases:\n\n- `HeaderStart` — before the label, in the header's flex layout. Drag grips, leading icons.\n- `HeaderEnd` — after the label, in the same flex layout. Sort buttons, menu triggers.\n- `HeaderEdge` — pinned to the column boundary, outside label layout. Resize handles.\n- `HeaderOverlay` — spans the whole header. Drop indicators, focus rings.\n\nDeclare slots inside the header; the table positions each one according to its slot type. Slot children receive the same render params as the header, so a sort button can read `column.field` to know which column it's acting on.\n\n```tsx\n<DataTableColumnHeader>\n  {() => 'Price'}\n  <HeaderEnd>{() => <button type=\"button\">Sort</button>}</HeaderEnd>\n</DataTableColumnHeader>\n```\n\nWhen a header has any slots, the label must be a render function — plain string children are only valid for headers without slots. That's why the example above writes `{() => 'Price'}` instead of just `Price`.\n\nThe shadcn wrapper's resize and reorder UI is built on these slots. See [Header Slots](/data-table/customization/header-slots/) for the placement diagram, the `component` prop alternative to render-function children, and complete recipes for sort, filter, and menu slots.\n\n<!-- File: packages/data-table/docs/3.columns/03.column-groups.md (Tokens: ~1609 | Category: anthropic-skill) -->\n---\ntitle: Column Groups\ndescription: Organize related columns under shared headers.\nsidebar:\n  label: Column Groups\n---\n\n`ColumnGroup` wraps adjacent columns and renders one shared header above them — \"Inventory\" sitting above stock and status, \"Fulfillment\" above region and category. In a wide table, the grouped header gives the reader a second-tier band to scan instead of counting columns from the left edge to locate a field. Use it when neighbouring columns belong together as a named bundle (inventory metrics, fulfillment details, customer attributes) and the table is wide enough that the bundle isn't obvious from column order alone.\n\n```tsx live wide\nimport { localModel } from '@virtuoso.dev/data-table'\nimport {\n  ColumnGroup,\n  ColumnGroupHeader,\n  DataTable,\n  DataTableCell,\n  DataTableColumn,\n  DataTableColumnHeader,\n} from '@/components/ui/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', category: 'Office', stock: 14, status: 'Ready', region: 'US' },\n  { name: 'USB-C Dock', category: 'Device', stock: 42, status: 'Ready', region: 'EU' },\n  { name: 'Keyboard', category: 'Device', stock: 8, status: 'Low', region: 'APAC' },\n]\n\nconst model = localModel({ data: rows })\nconst groupHeaderClassName = 'flex h-8 items-center justify-center bg-muted/40 px-2 text-xs font-medium text-muted-foreground'\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 300 }}>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Catalog'}</ColumnGroupHeader>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Inventory'}</ColumnGroupHeader>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n      <ColumnGroup>\n        <ColumnGroupHeader className={groupHeaderClassName}>{() => 'Fulfillment'}</ColumnGroupHeader>\n        <DataTableColumn field=\"region\">\n          <DataTableColumnHeader>Region</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </ColumnGroup>\n    </DataTable>\n  )\n}\n```\n\nGrouping changes only the header. Each column inside a group keeps its own field, cell renderer, width, visibility, and saved state — the body grid never notices the grouping exists. That separation is what makes the next sections work: every per-column feature still applies, with a few group-level twists worth knowing.\n\n## Sticky groups\n\nPin a whole group with `<ColumnGroup sticky=\"left\">` (or `\"right\"`). Every column inside follows the group to the edge during horizontal scroll — it's a shorthand for declaring `sticky=\"left\"` on each child, and the group header stays anchored above its columns.\n\n```tsx\n<ColumnGroup sticky=\"left\">\n  <ColumnGroupHeader>{() => 'Identity'}</ColumnGroupHeader>\n  <DataTableColumn field=\"id\">…</DataTableColumn>\n  <DataTableColumn field=\"name\">…</DataTableColumn>\n</ColumnGroup>\n```\n\nOnly top-level groups can be sticky — `sticky` on a nested `<ColumnGroup>` is ignored. Children inherit sticky-ness from the nearest sticky ancestor, so nested groups don't need their own opt-in.\n\n## Resizing inside a group\n\nResize handles work unchanged on columns inside groups. Each column owns its width override; the group header automatically re-sums from its children on every resize, so dragging \"Stock\" wider stretches the \"Inventory\" band to match. You never set a width on the group itself — there's no group-level resize API, because distributing pixels across child columns is rarely what users mean when they grab a header.\n\n## Reordering with groups\n\nTwo reorder gestures coexist:\n\n- **Move a whole group.** `DraggableGroupHeader` (from the shadcn `column-reorder` registry) makes the group header itself draggable. The user drags \"Inventory\" between \"Catalog\" and \"Fulfillment\" and all of Inventory's columns travel together, keeping their internal order.\n- **Move one column.** `ReorderGrip` on individual column headers keeps working — useful for nudging a column within its group.\n\nBoth gestures respect sticky regions: a left-sticky source can only land among other left-sticky targets, and so on. That guard is built into the registry components, so you can drop them in without filtering by hand.\n\nThere's a footgun in mixing the two: **a single-column drag that crosses a group boundary breaks the visual layout.** The header tree pairs each column with its declared `<ColumnGroup>` regardless of where it now sits in column order, so a column dragged out of group A and dropped between group B's columns produces a body row in the new order but a header band that's no longer contiguous. The whole-group drag never has this problem — it moves every key in one transaction.\n\nTwo practical responses:\n\n- Mount `ReorderGrip` only on ungrouped columns (or only inside groups, never both), so users can't initiate a cross-group single-column drag.\n- Or keep both gestures and accept that cross-group moves split the band. Works for tables where the grouping is informational rather than structural.\n\n`reorderColumnGroup$` is the underlying engine action if you wire your own group-drag UI; `DraggableGroupHeader` publishes to it. See [Column Reordering](/data-table/columns/column-reordering/) for the full reorder surface.\n\n## Nesting\n\n`<ColumnGroup>` can wrap other `<ColumnGroup>` declarations for two-tier categorization — \"Customer\" above \"Personal\" and \"Account\", each above their own columns:\n\n```tsx\n<ColumnGroup>\n  <ColumnGroupHeader>{() => 'Customer'}</ColumnGroupHeader>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Personal'}</ColumnGroupHeader>\n    <DataTableColumn field=\"name\">…</DataTableColumn>\n    <DataTableColumn field=\"email\">…</DataTableColumn>\n  </ColumnGroup>\n  <ColumnGroup>\n    <ColumnGroupHeader>{() => 'Account'}</ColumnGroupHeader>\n    <DataTableColumn field=\"plan\">…</DataTableColumn>\n  </ColumnGroup>\n</ColumnGroup>\n```\n\nEach nesting level stacks one extra header row. Two levels usually covers what's worth grouping; deeper hierarchies steal vertical space the data needs.\n\n## Layout notes\n\n- Each nesting level adds a full header row. Keep group labels short and the row height modest — `text-xs` with small padding is the usual register.\n- Group headers render full-width over their span. The label sits inside that bar, so very long labels either wrap or get clipped depending on your CSS.\n\n<!-- File: packages/data-table/docs/3.columns/04.sticky-columns.md (Tokens: ~1083 | Category: anthropic-skill) -->\n---\ntitle: Sticky Columns\ndescription: Pin high-value columns to the left or right side of a wide table.\nsidebar:\n  label: Sticky Columns\n---\n\nPass `sticky=\"left\"` or `sticky=\"right\"` to a `DataTableColumn` and it stays in place while the rest of the row scrolls under it horizontally. Use this to keep row-identifying columns (typically a product name) visible on the left, and status or action columns visible on the right, when the table is wide enough to scroll sideways.\n\nTwo layout constraints have to hold for the effect to work:\n\n- Pinned columns need an explicit minimum width on the header (`min-w-[…]` on `DataTableColumnHeader`). Without one, the column can collapse to its content width and overlap the scrollable cells sliding past it. See [Column Resizing](/data-table/columns/column-resizing/#where-column-widths-live) for why width belongs on the header, not the cell.\n- Every pinned column subtracts from the scrollable area. Pin one or two columns per edge — pinning more shrinks the scroll region until the feature stops earning its keep.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  owner: ['Avery', 'Morgan', 'Riley'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n  supplier: ['Northwind', 'Contoso', 'Fabrikam'][index % 3]!,\n  warehouse: ['Denver', 'Rotterdam', 'Singapore'][index % 3]!,\n  price: 49 + (index % 8) * 24,\n  stock: (index * 7) % 64,\n  leadTime: `${2 + (index % 5)} days`,\n  margin: `${18 + (index % 6) * 3}%`,\n  updated: `2026-05-${String(1 + (index % 17)).padStart(2, '0')}`,\n}))\n\nconst model = localModel({ data: rows })\nconst scrollColumns = [\n  ['category', 'Category'],\n  ['owner', 'Owner'],\n  ['region', 'Region'],\n  ['supplier', 'Supplier'],\n  ['warehouse', 'Warehouse'],\n  ['price', 'Price'],\n  ['stock', 'Stock'],\n  ['leadTime', 'Lead time'],\n  ['margin', 'Margin'],\n  ['updated', 'Updated'],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\" sticky=\"left\">\n        <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      {scrollColumns.map(([field, label]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader className=\"min-w-[140px]\">{label}</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n      <DataTableColumn field=\"status\" sticky=\"right\">\n        <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe relevant lines, isolated:\n\n```tsx\n<DataTableColumn field=\"name\" sticky=\"left\">\n  <DataTableColumnHeader className=\"min-w-[180px]\">Product</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"status\" sticky=\"right\">\n  <DataTableColumnHeader className=\"min-w-[140px]\">Status</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n## Changing pinning at runtime\n\n`sticky` on `DataTableColumn` sets the initial pinning. When the user needs to pin or unpin a column from a toolbar — say, a \"Pin to left\" menu item — publish to the table's `setColumnSticky$` channel instead of re-rendering with a new prop. The [Sticky Columns example](/data-table/examples/sticky-columns/) wires up that external toggle end-to-end; [Controlling the Table](/data-table/controlling-the-table/) covers the engine API the channel belongs to.\n\n<!-- File: packages/data-table/docs/3.columns/05.column-visibility.md (Tokens: ~1857 | Category: anthropic-skill) -->\n---\ntitle: Column Visibility\ndescription: Hide and restore columns with declarative defaults, reactive controls, and persistence.\nsidebar:\n  label: Column Visibility\n---\n\nColumn visibility has two flavours: a declarative default that hides a column at mount, and a runtime channel that flips visibility from a \"Columns\" menu, a saved view, or a similar toggle. The two compose — declare hidden defaults for columns like internal IDs, audit timestamps, and other secondary metadata, then expose a runtime control for users who need to opt back in.\n\n```tsx live wide\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { columns$, localModel, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, resetColumnVisibility$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\n\nconst rows = [\n  { product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n]\n\nfunction labelFor(field: string) {\n  return field.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const columns = useRemoteCellValue(columns$, engineRef)\n  const visibility = useRemoteCellValue(columnVisibilityState$, engineRef)\n  const setColumnVisibility = useRemotePublisher(setColumnVisibility$, engineRef)\n  const resetColumnVisibility = useRemotePublisher(resetColumnVisibility$, engineRef)\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"flex flex-wrap items-center gap-3 rounded-lg border bg-muted/30 px-3 py-2 text-sm\">\n        {[...(columns ?? new Map())].map(([key, column]) => {\n          const visible = visibility?.get(key) ?? column.visible !== false\n\n          return (\n            <label key={key} className=\"flex items-center gap-2\">\n              <input checked={visible} onChange={() => setColumnVisibility({ key, visible: !visible })} type=\"checkbox\" />\n              {labelFor(column.field)}\n            </label>\n          )\n        })}\n        <Button onClick={() => resetColumnVisibility()} size=\"sm\" variant=\"outline\">\n          Reset\n        </Button>\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 280 }}>\n        <DataTableColumn field=\"product\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"stock\">\n          <DataTableColumnHeader className=\"justify-end\">Stock</DataTableColumnHeader>\n          <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"internalId\" visible={false}>\n          <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n## Hiding at declaration time\n\nPass `visible={false}` to a `DataTableColumn` and it stays out of the rendered grid:\n\n```tsx\n<DataTableColumn field=\"internalId\" visible={false}>\n  <DataTableColumnHeader>Internal ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nThe column is still _registered_ — that's what makes it selectable in a picker. It just doesn't take a slot in the header, body, or scroll measurement. There's no layout cost beyond keeping its JSX in the tree, so this is also the right shape for \"hidden by default, sometimes useful\" columns that don't need a runtime toggle.\n\n## Toggling at runtime\n\nPublish to `setColumnVisibility$` (from `@virtuoso.dev/data-table/column-visibility`) with a runtime column key and the boolean you want the column to end up at:\n\n```tsx\nsetColumnVisibility({ key, visible: !isVisible })\n```\n\nRead the current state from `columnVisibilityState$` — a `Map<key, boolean>` with one entry per registered column, declarative defaults already folded in. Consumers don't have to fall back to `columns$` for \"what's the default again?\"; the resolved value is right there.\n\n`resetColumnVisibility$` (a `Trigger`, no payload) clears every override at once — typically wired to a \"Reset to defaults\" button. And there's a quieter convenience worth knowing: when the user toggles a column back to its declarative default, the override is dropped instead of stored as a redundant entry. \"Hide then show\" returns the table to a clean state, and the saved-state side of the feature stays minimal as a result.\n\n## Where the controls live\n\nThe demo above sits beside the table and connects through `engineRef`, using `useRemoteCellValue` and `useRemotePublisher`. That's the common shape — a toolbar, a preset menu, a settings panel — anywhere the controls aren't a child of `<DataTable>`.\n\nFor controls mounted _inside_ the table tree — a header slot, a footer, a context menu rendered from a cell — the simpler `useCellValue` and `usePublisher` hooks from `@virtuoso.dev/reactive-engine-react` work, picking up the same engine through React context:\n\n```tsx\nimport { columns$ } from '@virtuoso.dev/data-table'\nimport { columnVisibilityState$, setColumnVisibility$ } from '@virtuoso.dev/data-table/column-visibility'\nimport { useCellValue, usePublisher } from '@virtuoso.dev/reactive-engine-react'\n\nfunction ColumnVisibilityPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div>\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key)\n        return (\n          <button key={key} onClick={() => setColumnVisibility({ key, visible: !visible })} type=\"button\">\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n```\n\n[Controlling the Table](/data-table/controlling-the-table/) covers the in-tree-vs-sibling distinction in depth.\n\n## How visibility composes with other column features\n\n- **Groups.** Hiding a column shrinks its `ColumnGroup` header band to whatever children remain visible. Hide every column in a group and the group header disappears with them — there's no empty band.\n- **Sticky.** A hidden sticky column simply vanishes; the columns next to it stay where they are rather than sliding into the pinned region.\n- **Reorder.** Visibility and order are independent — hiding a column doesn't change its position in `columns$`, and showing it again restores it to the slot it held before.\n\n## Saving the user's choice\n\n`columnVisibilityPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to save visibility across reloads. The saved shape is keyed by stable field name and contains only the _differences from declarative defaults_, so:\n\n- A user who hasn't touched the picker saves nothing.\n- A column that started visible and stays visible never appears in saved state.\n- Hiding a default-visible column writes one entry; un-hiding it drops the entry back out.\n\nThat makes the saved bundle forward-compatible: adding a new column to the JSX with a sensible declarative default doesn't surprise existing users with a stale override on next load. The catch sits on the other side — renaming a `field` invalidates the saved visibility for that field, since the saved state can no longer find the column it was about. The [field-name persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\nSee the [Column Visibility example](/data-table/examples/column-visibility/) for a runnable sibling-toolbar setup.\n\n<!-- File: packages/data-table/docs/3.columns/06.column-resizing.md (Tokens: ~1888 | Category: anthropic-skill) -->\n---\ntitle: Column Resizing\ndescription: Let users change column widths with the opt-in resize model and shadcn resize handle.\nsidebar:\n  label: Column Resizing\n---\n\nA column's default width comes from its content and the table's layout — usually fine, sometimes not. Long product names need more room than the default; an ID column can shrink. Column resizing gives users a drag handle on the header edge to set the width themselves, and the table remembers their choice once persistence is wired up.\n\n## Adding the drag handle\n\nMount `ResizeHandle` from the shadcn `data-table-resize-handle` registry item inside a `HeaderEdge` slot. The handle owns the pointer interaction, the live preview while dragging, and the publish of the final width back to the table — the column declaration only has to expose the slot.\n\n```bash\nnpx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle\n```\n\nThe registry item name is hyphenated; it installs the nested import path `@/components/ui/data-table/column-resize`.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 60 }, (_, index) => ({\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  stock: 8 + ((index * 7) % 41),\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 340 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe slot alone is what enables resizing on a column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader>\n    <HeaderEdge component={ResizeHandle} />\n    {() => 'Product'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nSkip `HeaderEdge` for columns the user shouldn't be allowed to resize — a fixed-width row-selector column, for example.\n\n## Where column widths live\n\nThe table owns every column's width through its header. It measures each `DataTableColumnHeader`, distributes any leftover viewport space across the visible columns, and uses the resulting size for every body cell in that column. The drag handle, the programmatic `resizeColumn$` call, and the persisted overrides feed into that same pipeline as user-selected rendered widths. While a resize override is active, sibling columns keep their current rendered widths so dragging a boundary feels direct and can create horizontal overflow.\n\nTo set a column's _base_ width, style the header with width or minimum-width classes. To decide which columns receive leftover horizontal space, use `DataTableColumn grow={number}`. Do not use Tailwind flex growth classes as the table sizing API:\n\n```tsx\n<DataTableColumn field=\"id\">\n  <DataTableColumnHeader className=\"w-20\">ID</DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-48\">Product</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nFor the full sizing model, including fixed metadata columns, text-heavy grow columns, horizontal overflow, and resize override behavior, see [Column Layout](/data-table/columns/column-layout/).\n\n:::caution[Don't put widths on the cell]\nBody cells render inside a grid track sized to the column's measured width — they're already constrained from the outside. Width CSS on `DataTableCell` (`w-*`, `min-w-*`, `max-w-*`, `flex-*`, `basis-*`) doesn't influence the column and can fight the grid into clipped or overflowing content. When a column needs to be wider, widen its header. Reserve cell CSS for things inside the track: padding, alignment, typography, truncation.\n:::\n\nThis includes width pressure added for \"realistic\" content. If a prompt name, badge stack, action menu, or timestamp needs room, express that as a header width:\n\n```tsx\n<DataTableColumn field=\"updated_at\">\n  <DataTableColumnHeader className=\"min-w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror that width on the body cell. For multi-line or nested content, place layout helpers inside the cell renderer:\n\n```tsx\n<DataTableCell>\n  {({ row }) => (\n    <div className=\"min-w-0\">\n      <p className=\"truncate\">{row.data.name}</p>\n      <p className=\"truncate text-muted-foreground\">{row.data.slug}</p>\n    </div>\n  )}\n</DataTableCell>\n```\n\nAs a final review step, search for width utilities on cell declarations:\n\n```bash\nrg 'DataTableCell.*className=.*(w-|min-w|max-w|basis-|grow|shrink|flex-(none|auto|initial|1|\\[))'\n```\n\n## Resizing without the drag handle\n\nSome resizes don't originate from the column edge. A \"fit to content\" toolbar button, a \"Compact / Comfortable / Spacious\" preset, a keyboard shortcut — each needs to set the width without anyone dragging. Publish to `resizeColumn$` directly:\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst resizeColumn = useRemotePublisher(resizeColumn$, engineRef)\n\nfunction fitNameColumn() {\n  const nameKey = [...(columns ?? new Map())].find(([, column]) => column.field === 'name')?.[0]\n  if (nameKey) {\n    resizeColumn({ key: nameKey, width: 280 })\n  }\n}\n```\n\nThe payload's `key` is the column's internal identifier, not its `field`. The lookup against `columns$` translates a stable, user-facing field name into the key the stream expects.\n\n## Persisting widths across reloads\n\nThe handle records the user's _intent_ — \"the user wants the name column 280px wide.\" Intent is what gets saved. Automatic `grow` output is not persisted; clearing the override lets the column fall back to its measured base width and the current grow distribution.\n\nMount `columnWidthPersistenceAdapter()` on `DataTableStatePersistence` to persist overrides. The adapter saves them keyed by `field`, so the saved entry survives reordering, partial column lists, and dynamically generated columns. [State Persistence](/data-table/state-persistence/) covers the full wiring, including the field-name contract that ties saved state to column declarations.\n\n## The full resize surface\n\nThree publishers cover the runtime mutations:\n\n- `resizeColumn$` — set a width override for one column.\n- `clearColumnWidthOverride$` — drop one override; the column falls back to its default.\n- `resetColumnWidthOverrides$` — drop every override at once, e.g. a \"Reset widths\" menu item.\n\nOne adapter wires those into persistence:\n\n- `columnWidthPersistenceAdapter()` — saves the override map; see [State Persistence](/data-table/state-persistence/).\n\nOne read-only cell exposes the realized layout, for layout-aware UI that needs the actual rendered widths:\n\n- `columnWidths$` — realized widths after distribution. Read for layout introspection; do **not** persist these values.\n\nThe [Column Resizing example](/data-table/examples/column-resizing/) puts the handle, preset menus, and persistence together in one table.\n\n<!-- File: packages/data-table/docs/3.columns/07.column-reordering.md (Tokens: ~1822 | Category: anthropic-skill) -->\n---\ntitle: Column Reordering\ndescription: Let users drag columns into the position that suits them, with persistence and engine-level controls behind the same protocol.\nsidebar:\n  label: Column Reordering\n---\n\nColumn reordering lets the user move columns into a different position than the JSX declares. The order in JSX becomes the initial order; the user changes it through a drag UI mounted in the header, a toolbar action, or programmatic engine calls.\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 40 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,\n  region: ['US', 'EU', 'APAC'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\nconst columns = [\n  ['name', 'Product', 'font-medium'],\n  ['category', 'Category', ''],\n  ['status', 'Status', ''],\n  ['region', 'Region', ''],\n] as const\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 320 }}>\n      {columns.map(([field, label, cellClassName]) => (\n        <DataTableColumn key={field} field={field}>\n          <DataTableColumnHeader>\n            <HeaderStart component={ReorderGrip} />\n            <HeaderOverlay component={ReorderDropZone} />\n            {() => label}\n          </DataTableColumnHeader>\n          <DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      ))}\n    </DataTable>\n  )\n}\n```\n\n## Wiring the handles\n\nTwo pieces mount inside each column header: a draggable grip the user can grab, and a drop zone that paints the indicator line when another column is dragged over. They live in two different header slots so they don't fight each other for space — the grip sits in `HeaderStart` next to the title, and the drop indicator overlays the whole header through `HeaderOverlay` without disturbing layout.\n\n```tsx\nimport { HeaderOverlay, HeaderStart } from '@/components/ui/data-table'\nimport { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'\n;<DataTableColumn field=\"status\">\n  <DataTableColumnHeader>\n    <HeaderStart component={ReorderGrip} />\n    <HeaderOverlay component={ReorderDropZone} />\n    {() => 'Status'}\n  </DataTableColumnHeader>\n  <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\n`ReorderGrip` and `ReorderDropZone` are shadcn registry components — drop them into a column header and reordering works. They're not the whole story, though: under them is an engine action any other surface can publish to.\n\n## Reordering from a toolbar or menu\n\nDrag isn't the only way to move columns. A \"Move to first\" menu item or an arrow-key shortcut can reach for `reorderColumns$` from outside the header. The shape is the same as what the drag UI publishes: source, target, and a `before` / `after` position.\n\n```tsx\nimport { columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { reorderColumns$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst columns = useRemoteCellValue(columns$, engineRef)\nconst reorderColumns = useRemotePublisher(reorderColumns$, engineRef)\n\nconst entries = [...(columns ?? new Map())]\nconst statusKey = entries.find(([, column]) => column.field === 'status')?.[0]\nconst nameKey = entries.find(([, column]) => column.field === 'name')?.[0]\n\nif (statusKey && nameKey) {\n  reorderColumns({ sourceKey: statusKey, targetKey: nameKey, position: 'before' })\n}\n```\n\nThe two-step lookup is genuine API friction worth knowing: `reorderColumns$` operates on runtime _column keys_ (the identity the engine uses internally), not the `field` names you write in JSX. Most callers know the field — \"move 'status' before 'name'\" — and translate by walking `columns$` once. Column keys are stable for a given declaration, so for repeated programmatic moves it's worth building the field → key map once at mount and stashing it in a ref.\n\n## Resetting to declaration order\n\nFor a \"Reset to default order\" button, publish `resetColumnOrderToDeclaration$`. The table already tracks the current component declaration order internally, so the caller doesn't need to snapshot runtime column keys at mount.\n\n```tsx\nimport { useEngineRef, useRemotePublisher } from '@virtuoso.dev/data-table'\nimport { resetColumnOrderToDeclaration$ } from '@virtuoso.dev/data-table/column-reorder'\n\nconst engineRef = useEngineRef()\nconst resetColumnOrder = useRemotePublisher(resetColumnOrderToDeclaration$, engineRef)\n\n;<button onClick={() => resetColumnOrder()} type=\"button\">\n  Reset order\n</button>\n```\n\nIf `columnOrderPersistenceAdapter()` is mounted, the reset is treated like any other order change and the declaration-order state is saved under the table's persistence key.\n\n## Sticky and group constraints\n\nTwo rules narrow what the drag UI accepts, and they apply equally to engine-level reorders:\n\n- **Sticky stays with sticky.** A drag from a `sticky=\"left\"` column only accepts drops on other left-sticky targets; right-sticky behaves the same. Non-sticky columns can't be pulled into a pinned region by the drag UI, and the engine action treats sticky-ness as a column property rather than a positional one — moving a non-sticky column next to a pinned one doesn't make it pinned.\n- **Group drag is its own gesture.** When columns live inside a `ColumnGroup`, the group header itself can be made draggable with `DraggableGroupHeader`, moving every member as one transaction. The single-column gesture stays available, but dragging an individual column across group boundaries breaks the visual band — see [Column Groups → Reordering with groups](/data-table/columns/column-groups/#reordering-with-groups) for the full picture and the practical mitigations.\n\n## Saving the user's order\n\n`columnOrderPersistenceAdapter()` plugs into [State Persistence](/data-table/state-persistence/) to keep the user's order across reloads. The saved shape is a list of `field` names — stable, declarative identifiers — rather than the runtime keys the engine uses internally. As a result:\n\n- Adding a new column to the JSX places it at its declared position on next load. Saved state doesn't list it, so the new column doesn't get a stale slot somewhere arbitrary.\n- Removing a column drops it from saved state without disturbing the others.\n- Renaming a `field` invalidates that entry's saved position — the saved state can't find the column it was about. [Field names are the persistence contract](/data-table/state-persistence/#field-names-are-the-persistence-contract) covers the rules around renames.\n\n## Building your own drag UI\n\nThe shadcn grip and drop zone are a reference implementation. If your design system uses a different drag library, you want long-press for touch, or the drop indicator needs custom positioning, the underlying coordination primitives are public:\n\n- `columnDragState$` — a reactive cell holding \"what's being dragged, where's the drop indicator.\" Grip writes, drop zone reads.\n- `beginColumnDrag$` / `endColumnDrag$` — lifecycle channels published from the grip's `dragstart` / `dragend`.\n- `setColumnDropTarget$` — what the drop zone publishes from `dragover`.\n\nThe source for `ReorderGrip` and `ReorderDropZone` in the shadcn registry is short and self-contained; copy it as a starting point and swap out the markup that doesn't fit.\n\nSee the [Column Reordering example](/data-table/examples/column-reorder/) for a runnable setup with persistence wired in.\n\n<!-- File: packages/data-table/docs/3.columns/08.runtime-columns.md (Tokens: ~2071 | Category: anthropic-skill) -->\n---\ntitle: Generating Columns at Runtime\ndescription: Discover and declare table columns when the field list is only known after data arrives.\nsidebar:\n  label: Runtime Columns\n---\n\nSome tables cannot name every column at build time. An admin viewer may render tenant-defined fields, a debug tool may inspect arbitrary JSON, or a report may receive its visible fields from the first data result.\n\n`DynamicColumns` is for that case. It waits for the first non-empty data result, calls your render function with that data, and lets you return normal `DataTableColumn` declarations. The columns stay fixed for the lifetime of that table instance — if the dataset shape changes later, remount the table with a new `key`.\n\n## What the render function receives\n\nThe render function is called with `{ data, model }`:\n\n- `data` is the first non-empty array the table captured. It does not change when the model later fetches more rows, filters, or fills in remote placeholders — you scan it once and produce columns from what's there.\n- `model` is the same handle you passed to `DataTable`, available for the rare case where a generated header or cell needs to publish a model action.\n\nField scanning lives in your app code so the rules — which fields to include, what counts as a sample value, how to handle group rows — stay obvious next to the table. A general-purpose starting point looks like this:\n\n```ts\ntype RuntimeField = {\n  field: string\n  sampleValue: unknown\n}\n\nfunction fieldsFromRows<Row>(rows: readonly Row[], includeField: (field: string) => boolean = () => true): RuntimeField[] {\n  const fields: RuntimeField[] = []\n  const fieldByName = new Map<string, RuntimeField>()\n  const hasSample = new Set<string>()\n\n  for (const row of rows) {\n    if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n      continue\n    }\n\n    for (const [field, value] of Object.entries(row)) {\n      if (!includeField(field)) {\n        continue\n      }\n\n      let descriptor = fieldByName.get(field)\n      if (!descriptor) {\n        descriptor = { field, sampleValue: value ?? null }\n        fieldByName.set(field, descriptor)\n        fields.push(descriptor)\n      }\n\n      if (!hasSample.has(field) && value !== null && value !== undefined) {\n        descriptor.sampleValue = value\n        hasSample.add(field)\n      }\n    }\n  }\n\n  return fields\n}\n```\n\nThat helper preserves first-seen field order and returns `{ field, sampleValue }` for each field. `sampleValue` is the first non-nullish value seen for that field, or `null` if the field exists but only has nullish values in the captured data.\n\n## Local data\n\nStatic columns declared around `DynamicColumns` keep their positions even though the runtime columns are produced between them. This example pins `id` on the left and `actions` on the right, and lets the table fill in everything else from the first local result.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { DynamicColumns, localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14 },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, region: 'US' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, region: 'EU' },\n]\n\ntype Product = (typeof rows)[number]\n\nconst model = localModel<Product>({ data: rows })\n\nfunction labelFor(field: string) {\n  return field.replaceAll('_', ' ').replace(/^\\w/, (letter) => letter.toUpperCase())\n}\n\nfunction fieldsToShow(rows: readonly Product[]) {\n  const fields: string[] = []\n  const seen = new Set<string>()\n\n  for (const row of rows) {\n    for (const field of Object.keys(row)) {\n      if (field !== 'id' && !seen.has(field)) {\n        seen.add(field)\n        fields.push(field)\n      }\n    }\n  }\n\n  return fields\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 300 }}>\n      <DataTableColumn field=\"id\" sticky=\"left\">\n        <DataTableColumnHeader className=\"w-24\">SKU</DataTableColumnHeader>\n        <DataTableCell className=\"text-muted-foreground tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n\n      <DynamicColumns<Product>>\n        {({ data }) =>\n          fieldsToShow(data).map((field) => (\n            <DataTableColumn field={field} key={field}>\n              <DataTableColumnHeader className=\"min-w-32\">{labelFor(field)}</DataTableColumnHeader>\n              <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n            </DataTableColumn>\n          ))\n        }\n      </DynamicColumns>\n\n      <DataTableColumn id=\"actions\" sticky=\"right\">\n        <DataTableColumnHeader className=\"w-24\">Actions</DataTableColumnHeader>\n        <DataTableCell>{({ row }) => <button type=\"button\">Open {row.data.id}</button>}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nThe `key={field}` on each generated column matters. Column state such as width, order, visibility, and sticky placement is tied to the field identity.\n\n## Remote data\n\nRemote tables derive their schema from the first non-empty result the same way local tables do. The descriptor helper above is reusable — the `sampleValue` lets the render prop pick alignment per field:\n\n```tsx\n<DataTable model={model}>\n  <DataTableColumn field=\"id\" sticky=\"left\" />\n\n  <DynamicColumns<ProductRow>>\n    {({ data }) =>\n      fieldsFromRows(data, (field) => field !== 'id').map(({ field, sampleValue }) => (\n        <DataTableColumn field={field} key={field}>\n          <DataTableColumnHeader>{labelFor(field)}</DataTableColumnHeader>\n          <DataTableCell {...(typeof sampleValue === 'number' ? { className: 'text-right tabular-nums' } : {})}>\n            {({ cellValue }) => String(cellValue ?? '')}\n          </DataTableCell>\n        </DataTableColumn>\n      ))\n    }\n  </DynamicColumns>\n\n  <DataTableColumn id=\"actions\" sticky=\"right\" />\n</DataTable>\n```\n\nThe headers appear after the first non-empty result resolves. Later pages or viewport fetches do not add more columns — that avoids mid-scroll layout changes, but it means the first captured result must contain every field you want to show.\n\nWhen the backend can return column metadata alongside the rows, prefer that. Metadata can render headers before any rows arrive, works for empty responses, and can carry labels, alignment, and visibility hints out of band.\n\n## Placeholders\n\nThe field scanner has no way to tell offset-mode placeholders apart from real rows — it treats them as ordinary data. Placeholder-only fields show up in the discovered column list, and fields that exist only on real rows go missing if the first captured result is all placeholders.\n\nMake placeholders schema-compatible with real rows:\n\n```ts\nconst placeholder = {\n  id: '',\n  product: '',\n  category: '',\n  stock: null,\n}\n```\n\nDo not put placeholder-only fields in the visible schema. If a placeholder needs technical fields for rendering, filter them with `includeField`. If the real field list cannot be known until the backend responds, return column metadata from the backend or wait for a real data result instead of relying on placeholder shape.\n\n## Grouped data\n\nGrouped models mix group-header rows and body rows in the same array. The field scanner treats them the same way, so any field on a group-header row (a `groupLabel`, a level number, a count) would become a body column unless you filter group rows out first:\n\n```ts\nconst fields = fieldsFromRows(\n  data.filter((row) => !(typeof row === 'object' && row !== null && 'groupLabel' in row)),\n  (field) => field !== 'id'\n)\n```\n\nRender group labels with `DataTableGroupHeader`; keep runtime columns focused on ordinary body rows.\n\n## Rediscovering columns\n\n`DynamicColumns` is intentionally one-shot. A user action that changes row values should not reshuffle the table's columns.\n\nWhen a user intentionally switches to a different dataset shape, remount the table:\n\n```tsx\n<DataTable key={datasetId} model={model}>\n  <DynamicColumns>{/* declare columns from the new first result */}</DynamicColumns>\n</DataTable>\n```\n\nA user-facing column-management UI — letting the user add, remove, or reorder fields after mount — is a different problem. Keep the field list in your own React state and map it to `DataTableColumn` declarations directly; `DynamicColumns` doesn't help here, and trying to bend it to that shape leads to the temporal bugs the one-shot rule was designed to avoid.\n\n## When this does not fit\n\n- **Backend-defined schemas.** Return column metadata alongside the rows when headers must render before data, when valid responses can be empty, or when labels and alignment come from server configuration.\n- **Statically-known schemas.** Use a static field list when the schema is known in code but the JSX would be repetitive — see [Generated columns](/data-table/columns/defining-columns/#generated-columns).\n- **Runtime column-management UI.** Lift the field list into your own React state when the user can add, remove, or reorder fields after mount.\n\n`DynamicColumns` fits when the schema is data-defined, the first non-empty result is representative, and remounting the table is acceptable for re-discovery.\n\n<!-- File: packages/data-table/docs/3.columns/09.column-layout.md (Tokens: ~988 | Category: anthropic-skill) -->\n---\ntitle: Column Layout\ndescription: Set base column widths and choose which columns grow into extra horizontal space.\nsidebar:\n  label: Column Layout\n---\n\nColumn width has two parts: the base width and the realized width.\n\nThe base width comes from the column header. The table measures `DataTableColumnHeader`, then uses that measured width as the starting point for both the header and every body cell in that column.\n\nThe realized width is the width the column gets after the table compares all base widths with the viewport. When the viewport is wider than the total base width, extra space is distributed by the table. When the viewport is narrower, columns keep their base widths and the table scrolls horizontally.\n\n## Set Base Widths On Headers\n\nUse header width classes for the minimum useful size of each column:\n\n```tsx\n<DataTableColumn field=\"name\">\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n```\n\nDo not mirror those widths on `DataTableCell`. Body cells render inside tracks sized by the table. Cell width utilities can fight those tracks and cause clipping or overlap.\n\n## Choose Which Columns Grow\n\nUse `DataTableColumn grow={number}` for columns that should absorb extra viewport width. The value is a ratio, not a CSS flex shorthand.\nOmitting `grow`, or passing `grow={0}`, keeps the column fixed when another visible column grows.\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-72\">Name</DataTableColumnHeader>\n  <DataTableCell>{NameCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"description\" grow={3}>\n  <DataTableColumnHeader className=\"min-w-80\">Description</DataTableColumnHeader>\n  <DataTableCell>{DescriptionCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"versions\">\n  <DataTableColumnHeader className=\"w-40\">Versions</DataTableColumnHeader>\n  <DataTableCell>{VersionBadgesCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn field=\"updated\">\n  <DataTableColumnHeader className=\"w-44\">Updated</DataTableColumnHeader>\n  <DataTableCell>{UpdatedCell}</DataTableCell>\n</DataTableColumn>\n\n<DataTableColumn id=\"actions\">\n  <DataTableColumnHeader className=\"w-16 justify-center\">Actions</DataTableColumnHeader>\n  <DataTableCell>{ActionsCell}</DataTableCell>\n</DataTableColumn>\n```\n\nIn this layout, `name` and `description` receive all extra width. `description` receives three times as much extra width as `name`. `versions`, `updated`, and `actions` keep their base widths.\n\nChoose grow columns from the local data and workflow. IDs, slugs, checkboxes, action menus, short labels, badges, version counts, timestamps, and compact metadata usually should not grow. Names, titles, descriptions, summaries, messages, notes, paths, and other text-heavy content usually benefit from extra room. The field name is only a clue: a short code-like `name` may be fixed, while a user-authored label column may deserve room.\n\nDo not make every column grow. The point of `grow` is to let useful text columns breathe while compact columns stay compact.\n\n## What Happens Without Grow\n\nTables that do not set `grow` preserve the default auto-fill behavior: when the viewport is wider than the total base width, extra space is distributed evenly across visible columns. This keeps existing tables filling their container without changing every column declaration.\n\nOnce any visible column has a positive finite `grow` value, only grow columns receive leftover width. Non-grow columns keep their base widths.\n\n## There Is No Shrink Prop\n\n`grow` only distributes extra space. It does not shrink columns below their base widths, and there is no `shrink` table API.\n\nWhen the viewport narrows, grow space disappears first. After the total base width no longer fits, the table keeps base widths and uses horizontal scrolling.\n\n## Resizing Grow Columns\n\nColumn resizing sets the rendered width of that column. During and after a resize override, the table preserves the current rendered widths of the other columns instead of redistributing grow space on every pointer move. That keeps the resize handle under the pointer and lets wider columns push the table into horizontal overflow.\n\nClear the resize override to return the column to its measured base width and the normal `grow` distribution.\n\nPersist resize overrides as user-selected widths, not as automatic grow output. The saved width is the user's explicit override; automatic grow space is recalculated only when no resize override is active for that layout.\n\n<!-- File: packages/data-table/docs/3.columns/index.md (Tokens: ~1096 | Category: anthropic-skill) -->\n---\ntitle: Overview\ndescription: Define the visible columns, headers, and cells that turn model rows into a grid.\nsidebar:\n  order: 0\n  label: Overview\n---\n\nColumns decide which fields are visible, in what order, at what width, and how each cell is rendered. You declare them as JSX children of `DataTable` — one `DataTableColumn` per visible field, with its header and cell renderer inside.\n\nThe JSX reads like a static description of the table, but `DataTableColumn` and its children don't render in place. Each one registers a definition with the table, which then draws the virtualized header row and the visible body cells from those definitions. That indirection is what makes virtualization possible: a single cell renderer is reused across many row positions as the user scrolls, and headers compose cleanly with sticky pinning, resizing, slots, and visibility.\n\n## Basic columns\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', price: 699, stock: 14 },\n  { id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', price: 229, stock: 42 },\n  { id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', price: 169, stock: 28 },\n]\n\nconst currency = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n})\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium\">{row.data.name}</span>\n              <span className=\"text-xs text-muted-foreground\">{row.data.id}</span>\n            </div>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"price\">\n        <DataTableColumnHeader className=\"justify-end\">Price</DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => currency.format(Number(cellValue))}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ row }) => (\n            <span className={row.data.stock < 20 ? 'font-medium text-amber-600' : 'text-foreground'}>\n              {row.data.stock < 20 ? 'Low stock' : 'Healthy'}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## What each part defines\n\n- `DataTableColumn` — one column, keyed by `field`.\n- `DataTableColumnHeader` — header content and header class.\n- `DataTableCell` — body-cell renderer and cell class.\n\nOmit the header to fall back to the field name; omit the cell to fall back to the field value.\n\n`field` is both the cell lookup key (`row.data[field]`) and the column's persistent identity. Keep it stable across releases if you use any column features that save state — see [State Persistence](/data-table/state-persistence/#field-names-are-the-persistence-contract) for the rules around renaming.\n\nFor layout, put base widths on `DataTableColumnHeader` and use `DataTableColumn grow={...}` only for columns that should absorb extra horizontal space. See [Column Layout](/data-table/columns/column-layout/) for the sizing model and guidance on choosing fixed vs growing columns from the data being displayed.\n\n## Where to go next\n\nDeclarative shape:\n\n- [Defining Columns](/data-table/columns/defining-columns/) — `field`, default rendering, declaration order, visibility, generated columns.\n- [Formatting Cells and Headers](/data-table/columns/cell-and-header-renderers/) — render params, extracted renderer functions, custom header content.\n- [Column Layout](/data-table/columns/column-layout/) — base widths, `grow` ratios, fixed metadata columns, and horizontal overflow.\n- [Column Groups](/data-table/columns/column-groups/) — wrap adjacent columns under a shared header.\n- [Sticky Columns](/data-table/columns/sticky-columns/) — pin a column to the left or right edge during horizontal scrolling.\n\nInteractive layout:\n\n- [Column Visibility](/data-table/columns/column-visibility/) — declarative defaults, runtime toggling, \"Columns\" picker.\n- [Column Resizing](/data-table/columns/column-resizing/) — drag handle in the header, presets, auto-fit.\n- [Column Reordering](/data-table/columns/column-reordering/) — drag-and-drop individual columns or whole groups.\n\nDynamic schemas:\n\n- [Generating Columns at Runtime](/data-table/columns/runtime-columns/) — discovering and declaring columns when the list isn't known up front.\n\n<!-- File: packages/data-table/docs/4.grouped-rows.md (Tokens: ~2792 | Category: anthropic-skill) -->\n---\ntitle: Grouped Rows\ndescription: Show grouped sections inside the scrolling row stream, with optional UI for the user to choose the grouping at runtime.\nsidebar:\n  label: Grouped Rows\n---\n\nWhen records belong to natural sections — products by category, invoices by customer, employees by department — group headers split the scrolling row stream into visible groups. Users see one continuous list; the headers anchor to the top of each section as new rows scroll into view.\n\n(This page is about row-level grouping. For grouping in the column header row, see [column groups](/data-table/columns/column-groups/).)\n\n## Letting users change groups\n\nThe common case: a \"Group by\" control where the user picks the grouping at runtime. Click the buttons in this example to regroup the table:\n\n```tsx live\nimport { useState } from 'react'\n\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, GroupHeaderCell } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ninterface Product {\n  id: string\n  name: string\n  category: 'Office' | 'Peripherals' | 'Audio'\n  status: 'In stock' | 'Low stock'\n}\n\ninterface ProductGroup {\n  label: string\n}\n\ntype GroupBy = 'none' | 'category' | 'status'\n\nconst products: Product[] = [\n  { id: 'SKU-001', name: 'Standing Desk', category: 'Office', status: 'In stock' },\n  { id: 'SKU-002', name: 'Desk Lamp', category: 'Office', status: 'Low stock' },\n  { id: 'SKU-003', name: 'USB-C Dock', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-004', name: 'Mechanical Keyboard', category: 'Peripherals', status: 'In stock' },\n  { id: 'SKU-005', name: 'Studio Monitor', category: 'Audio', status: 'Low stock' },\n  { id: 'SKU-006', name: 'Audio Interface', category: 'Audio', status: 'In stock' },\n]\n\nfunction isProduct(row: Product | ProductGroup): row is Product {\n  return 'id' in row\n}\n\nfunction rowKey(row: Product | ProductGroup) {\n  return isProduct(row) ? row.id : `group-${row.label}`\n}\n\nfunction groupProducts(rows: Product[], groupBy: GroupBy) {\n  if (groupBy === 'none') {\n    return rows\n  }\n\n  const buckets = new Map<string, Product[]>()\n  for (const row of rows) {\n    const key = groupBy === 'category' ? row.category : row.status\n    const bucket = buckets.get(key)\n    if (bucket) {\n      bucket.push(row)\n    } else {\n      buckets.set(key, [row])\n    }\n  }\n\n  const data: (Product | ProductGroup)[] = []\n  const groups: { index: number; level: number }[] = []\n\n  for (const [key, items] of buckets) {\n    groups.push({ index: data.length, level: 0 })\n    data.push({ label: `${key} (${items.length})` })\n    data.push(...items)\n  }\n\n  return { data, groups }\n}\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = (actionState?.group?.payload ?? 'category') as GroupBy\n\n  const options: { value: GroupBy; label: string }[] = [\n    { value: 'none', label: 'No groups' },\n    { value: 'category', label: 'Group by category' },\n    { value: 'status', label: 'Group by status' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <button\n          aria-pressed={groupBy === option.value}\n          className={`rounded-md border px-3 py-1 text-sm ${groupBy === option.value ? 'bg-foreground text-background' : 'bg-background'}`}\n          key={option.value}\n          onClick={() => dispatch({ action: 'group', payload: option.value })}\n        >\n          {option.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel<Product, ProductGroup>({\n      data: products,\n      pipeline: ['group'],\n      initialActions: [{ action: 'group', payload: 'category' }],\n      actions: {\n        group: {\n          stage: 'group',\n          handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-3\">\n      <GroupByControls engineRef={engineRef} />\n      <DataTable\n        className=\"rounded-xl\"\n        computeRowKey={({ data }) => rowKey(data as Product | ProductGroup)}\n        engineRef={engineRef}\n        model={model}\n        style={{ height: 320 }}\n      >\n        <GroupHeaderCell className=\"bg-muted/60 px-3 py-2 text-sm font-medium\">\n          {({ row }) => String((row.data as ProductGroup).label)}\n        </GroupHeaderCell>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nThe interactive grouping above does three things:\n\n- **Declares a `group` pipeline stage** on the model so the table knows where in its data pipeline regrouping runs. The pipeline lists the stages in order; `['group']` is the simplest case, but `['filter', 'sort', 'group']` is the typical production shape.\n- **Defines a `group` action** whose handler receives the current data and a payload (the user's selection) and returns the new `{ data, groups }` shape.\n- **Connects the controls through the table engine.** The buttons publish to `dispatchModelAction$`; the active button reads `modelActionState$`. The toolbar never stores a second copy of the grouping state.\n\nThe wire-up, isolated from the demo's rendering code, looks like this:\n\n```tsx\nconst [model] = useState(() =>\n  localModel<Product, ProductGroup>({\n    data: products,\n    pipeline: ['group'],\n    initialActions: [{ action: 'group', payload: 'category' }],\n    actions: {\n      group: {\n        stage: 'group',\n        handler: ({ data, payload }) => groupProducts(data.filter(isProduct), payload as GroupBy),\n      },\n    },\n  })\n)\n\nfunction GroupByControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const groupBy = actionState?.group?.payload ?? 'category'\n\n  return (\n    <button aria-pressed={groupBy === 'category'} onClick={() => dispatch({ action: 'group', payload: 'category' })}>\n      Group by category\n    </button>\n  )\n}\n```\n\nFor the full filter + sort + group combination — three controls sharing one pipeline — see [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for the runnable end-to-end recipe.\n\n## Pre-grouped data\n\nWhen the grouping is fixed at mount and won't change at runtime, skip the pipeline action and pass the already-flattened result directly:\n\n```tsx\nconst tableData = {\n  data: [\n    { label: 'Office (2)' },\n    { id: 'SKU-001', name: 'Standing Desk', category: 'Office' },\n    { id: 'SKU-002', name: 'Desk Lamp', category: 'Office' },\n  ],\n  groups: [{ index: 0, level: 0 }],\n}\n\nconst model = localModel(tableData)\n```\n\n`GroupHeaderCell` receives `row.data` for the marked entry, so it can read whatever fields you put on the group object (here, `label`).\n\n## How grouping is expressed\n\nThe table doesn't accept nested children. It receives one flattened row array plus markers identifying which entries are group headers:\n\n```ts\ntype GroupedRows<TData, TGroup> = {\n  data: Array<TData | TGroup>\n  groups: { index: number; level: number }[]\n}\n```\n\n- `data` mixes group rows and data rows in display order.\n- `groups[]` marks which positions in `data` are headers. Each marker carries an `index` (zero-based position in the flattened array) and a `level` (nesting depth).\n- The table renders marked entries through `GroupHeaderCell` instead of through column cells.\n\nGroup rows only need the fields your `GroupHeaderCell` reads. Data rows need the fields your columns read. The two shapes can differ; the table picks the renderer per entry based on the markers.\n\n## Multi-level grouping\n\nFor deeper hierarchy (region → category → product), use sequential markers with increasing levels:\n\n```ts\nconst groups = [\n  { index: 0, level: 0 }, // 'North America'\n  { index: 1, level: 1 }, //   'Office'\n  { index: 4, level: 1 }, //   'Peripherals'\n  { index: 7, level: 0 }, // 'Europe'\n  { index: 8, level: 1 }, //   'Office'\n]\n```\n\nHierarchy is implicit in row order: every level-1 marker belongs to the most recent level-0 marker that precedes it. Style nesting visually by branching on `row.level` inside `GroupHeaderCell`:\n\n```tsx\n<GroupHeaderCell>\n  {({ row }) => (\n    <div className={row.level === 0 ? 'bg-muted font-medium' : 'bg-muted/40 pl-6'}>{(row.data as { label: string }).label}</div>\n  )}\n</GroupHeaderCell>\n```\n\n`level` controls visual and sticky nesting only — it does not add parent-child data behavior. If you need to know which level-0 group a row belongs to, derive it from row order at the time you flatten.\n\n## Remote grouped data\n\nWhen the grouping is computed on the backend, the fetch result includes markers alongside the rows; `remoteModel` forwards them to the table. The backend returns a flattened grouped result, not nested children:\n\n```ts\nasync function fetchProducts(params: FetchParams<Query>) {\n  const result = await fetchGroupedRows(params)\n\n  return {\n    rows: result.rows,\n    groups: result.groups,\n    totalCount: result.totalCount,\n  }\n}\n```\n\nIn offset mode, `totalCount` is the length of the full flattened result (including group headers), and marker indexes are positions in that full result — not relative to the current page. In append mode, return markers for the row stream after each append. Group rows still need a shape your `GroupHeaderCell` can branch on.\n\n## Rules of thumb\n\n- Group rows are virtualized full-width rows — keep their content lightweight.\n- Give group rows stable keys via `computeRowKey` when grouping, sorting, or filtering can reorder them. Use a stable group identifier (`groupKey`, `region`, etc.), not the current group index.\n- The table doesn't compute groups for you. Either group your data before passing it in, or compute inside a pipeline stage.\n- `level` controls visual and sticky nesting. It does not add parent-child data behavior.\n\n## When this doesn't fit\n\n- **Nested source data.** Flatten it before passing to the table — the table's display contract is flat-with-markers, not nested children.\n- **User-toggleable expand/collapse.** Keep the expanded/collapsed state in your app and rebuild the flattened `data` and `groups` from it on each toggle.\n- **Remote grouping across unloaded ranges.** Compute the grouping on the backend, or return enough metadata for the client to place group headers correctly without seeing every row.\n\nSee [Group Rows by Region and Category](/data-table/examples/grouped-data/) for a complete pre-grouped local table, and [Build an In-memory Data Pipeline](/data-table/examples/local-filter-sort-group/) for grouping wired alongside filter and sort.\n\n<!-- File: packages/data-table/docs/5.state-persistence.md (Tokens: ~1898 | Category: anthropic-skill) -->\n---\ntitle: State Persistence\ndescription: Persist table feature state and model action state with opt-in adapters.\nsidebar:\n  label: State Persistence\n---\n\n`DataTableStatePersistence` saves column width, order, visibility, and model action state under a storage key, restoring them on mount. With it wired up, a user's resize, hide, drag, or filter survives a reload. Two requirements: mount the component inside the table, and pass an adapter for each feature you want persisted.\n\n## Minimal setup\n\n```tsx live wide\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'\nimport { ResizeHandle } from '@/components/ui/data-table/column-resize'\nimport { columns$, localModel, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport {\n  columnVisibilityPersistenceAdapter,\n  columnVisibilityState$,\n  setColumnVisibility$,\n} from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence } from '@virtuoso.dev/data-table/state-persistence'\n\nconst rows = [\n  { id: 'SKU-001', product: 'Standing Desk', category: 'Office', stock: 14, internalId: 'inv-001' },\n  { id: 'SKU-002', product: 'USB-C Dock', category: 'Peripherals', stock: 42, internalId: 'inv-002' },\n  { id: 'SKU-003', product: 'Mechanical Keyboard', category: 'Peripherals', stock: 28, internalId: 'inv-003' },\n  { id: 'SKU-004', product: 'Noise-canceling Headset', category: 'Audio', stock: 9, internalId: 'inv-004' },\n]\n\nconst model = localModel({ data: rows })\nconst adapters = [columnVisibilityPersistenceAdapter(), columnWidthPersistenceAdapter()]\n\nfunction ColumnPicker() {\n  const columns = useCellValue(columns$)\n  const visibility = useCellValue(columnVisibilityState$)\n  const setColumnVisibility = usePublisher(setColumnVisibility$)\n\n  return (\n    <div className=\"mb-3 flex flex-wrap gap-2 rounded-md border bg-muted/30 p-3 text-sm\">\n      {[...columns].map(([key, column]) => {\n        const visible = visibility.get(key) ?? column.visible !== false\n        return (\n          <button\n            aria-pressed={visible}\n            className={visible ? 'rounded border bg-background px-2 py-1 font-medium' : 'rounded border px-2 py-1 text-muted-foreground'}\n            key={key}\n            onClick={() => setColumnVisibility({ key, visible: !visible })}\n            type=\"button\"\n          >\n            {visible ? 'Hide' : 'Show'} {column.field}\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\nexport default function App() {\n  return (\n    <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} model={model} style={{ height: 360 }}>\n      <DataTableStatePersistence adapters={adapters} storageKey=\"docs-state-persistence-feature-page\" />\n      <ColumnPicker />\n\n      <DataTableColumn field=\"product\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Product'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"category\">\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Category'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"stock\">\n        <DataTableColumnHeader className=\"justify-end\">\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Stock'}\n        </DataTableColumnHeader>\n        <DataTableCell className=\"text-right tabular-nums\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"internalId\" visible={false}>\n        <DataTableColumnHeader>\n          <HeaderEdge component={ResizeHandle} />\n          {() => 'Internal ID'}\n        </DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nReload after hiding a column or resizing a header — the adapters you passed restore that state.\n\n## Adapters\n\n| Adapter                                | Subpath              | Persists                                                                                 |\n| -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |\n| `modelStatePersistenceAdapter()`       | `/state-persistence` | Action payloads from `localModel()` / `remoteModel()` (filters, sort, group)             |\n| `columnOrderPersistenceAdapter()`      | `/column-reorder`    | Column order, keyed by field                                                             |\n| `columnWidthPersistenceAdapter()`      | `/column-resize`     | Width overrides, keyed by field                                                          |\n| `columnVisibilityPersistenceAdapter()` | `/column-visibility` | Visibility overrides (only those differing from the declarative default), keyed by field |\n\n```tsx\nimport { columnOrderPersistenceAdapter } from '@virtuoso.dev/data-table/column-reorder'\nimport { columnWidthPersistenceAdapter } from '@virtuoso.dev/data-table/column-resize'\nimport { columnVisibilityPersistenceAdapter } from '@virtuoso.dev/data-table/column-visibility'\nimport { DataTableStatePersistence, modelStatePersistenceAdapter } from '@virtuoso.dev/data-table/state-persistence'\n\nconst adapters = [\n  columnVisibilityPersistenceAdapter(),\n  columnOrderPersistenceAdapter(),\n  columnWidthPersistenceAdapter(),\n  modelStatePersistenceAdapter(),\n]\n\n<DataTable model={model} style={{ height: 420 }}>\n  <DataTableStatePersistence adapters={adapters} storageKey=\"inventory-table\" />\n  {/* columns */}\n</DataTable>\n```\n\nStorage defaults to `window.localStorage`. Pass `storage` for `sessionStorage`, a test double, or an application-specific backend.\n\nThe `adapters` array must be stable — define it outside the component or wrap it in `useMemo()` so the persistence component doesn't resubscribe on every render.\n\n## Storage keys and resets\n\n`storageKey` identifies one logical table. Change it when the saved state should no longer apply (different product surface, breaking schema change).\n\n`resetKey` clears saved state without changing the identity — bump it when the user explicitly resets a view.\n\n```tsx\n<DataTableStatePersistence adapters={adapters} resetKey={resetVersion} storageKey=\"inventory-table\" />\n```\n\nAdapters preserve unmatched entries where they can, so state survives partially-matching dynamic schemas.\n\n## Field names are the persistence contract\n\nThe adapters above all key their saved state by `field`. A column's width entry is \"the width for the column whose `field` is `'price'`\", not \"the width for the column in position 2\". Same for order, visibility, and sticky pin. That's what makes saved state survive schema migrations, partial column lists, and dynamic column generation — the field name is the contract between the column declaration and the saved entry.\n\nTwo consequences fall out of that:\n\n**Don't rename `field` lightly.** A column declared as `field=\"price\"` last week and `field=\"cost\"` this week is two different columns from persistence's perspective. The width entry under `'price'` doesn't transfer to `'cost'`, and the user's saved width silently goes back to the default. For schema migrations that have to rename, plan a one-time migration step that rewrites saved entries from the old key to the new one.\n\n**Keep `field` separate from header copy.** Header text is presentation and changes freely (locale switches, A/B tests, restyling). `field` is identity and must stay the same:\n\n```tsx\n<DataTableColumn field=\"customerName\">\n  <DataTableColumnHeader>{locale === 'de' ? 'Kunde' : 'Customer'}</DataTableColumnHeader>\n</DataTableColumn>\n```\n\nThis separation is why persisted state survives locale changes — the saved width sits under `'customerName'` regardless of what the header reads in any given language.\n\n## Model action persistence\n\nOnly actions marked with `persistence` are saved. Add it to a pipeline action for local models, or to an action that transforms request params for remote models.\n\n```tsx\nconst model = remoteModel({\n  fetch: fetchProducts,\n  initialParams: { status: 'all' },\n  actions: {\n    filter: {\n      persistence: { isEmpty: (value) => value === 'all', key: 'status' },\n      handler: ({ params, payload }) => ({ ...params, status: payload }),\n    },\n  },\n})\n```\n\nThe adapter saves action payloads, not row data. On restore, the model replays the action and rebuilds the request or local pipeline result.\n\nSee the [State Persistence example](/data-table/examples/state-persistence/) for a remote table wiring model and column-width persistence together.\n\n<!-- File: packages/data-table/docs/6.controlling-the-table.md (Tokens: ~2775 | Category: anthropic-skill) -->\n---\ntitle: Controlling the Table\ndescription: Read table state and publish table actions from nearby toolbars, panels, and other application UI.\nsidebar:\n  label: Controlling the Table\n---\n\nExternal UI — toolbars, settings menus, status badges — often needs to read the table's state (current scroll position, visible columns, loading phase) and trigger its actions (scroll to a row, toggle a column, refresh data). The table exposes both through its internal reactive runtime; external UI taps that runtime directly instead of receiving lifted state through props.\n\nTwo pieces wire the connection. The first is an identifier for the table instance: `useEngineRef()` returns a ref for UI in the same component, and a string `engineId` reaches a table mounted elsewhere in the tree. The second is the channels the identifier carries — _state cells_ like `scrollLocation$` and `columns$` expose values you read with `useRemoteCellValue`, and _action streams_ like `scrollToRow$` and `setColumnVisibility$` accept commands you publish with `useRemotePublisher`. The `$` suffix marks both flavors.\n\n## Engine refs\n\nReach for `engineRef` when the external UI lives in the same component as the table — a toolbar two divs up, a status panel rendered as a sibling. `useEngineRef()` returns an opaque ref; pass it to `<DataTable engineRef={engineRef}>` and to the remote hooks beside it, and they resolve to the same engine.\n\n```tsx live\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel, scrollLocation$, scrollToRow$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst rows = Array.from({ length: 120 }, (_, index) => ({\n  id: `SKU-${String(index + 1).padStart(3, '0')}`,\n  name: `Product ${index + 1}`,\n  category: ['Office', 'Peripherals', 'Audio'][index % 3]!,\n}))\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const scrollLocation = useRemoteCellValue(scrollLocation$, engineRef)\n  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n          Top\n        </Button>\n        <Button onClick={() => scrollToRow({ index: 80, align: 'center', behavior: 'smooth' })} variant=\"outline\">\n          Center row 81\n        </Button>\n      </div>\n\n      <div className=\"rounded-lg border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n        {scrollLocation === undefined ? 'Waiting for scroll state...' : `Visible height: ${Math.round(scrollLocation.visibleListHeight)}px`}\n      </div>\n\n      <DataTable className=\"rounded-xl\" model={model} engineRef={engineRef} style={{ height: 360 }}>\n        <DataTableColumn field=\"name\">\n          <DataTableColumnHeader>Product</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"category\">\n          <DataTableColumnHeader>Category</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\nMount order doesn't matter. The hooks register against the ref immediately and start receiving values as soon as the table's engine comes up.\n\n## Engine IDs\n\nReach for `engineId` when the external UI lives _far_ from the table — a page-level toolbar in the layout shell, a sibling panel that doesn't share a parent component with the table, anything where threading a ref through props would mean prop-drilling through unrelated levels. Pick a stable string, pass it as `<DataTable engineId={…}>`, and hand the same string to the hooks anywhere in the tree.\n\n```tsx\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { scrollToRow$, useRemotePublisher } from '@virtuoso.dev/data-table'\n\nconst TABLE_ENGINE_ID = 'inventory-table'\n\nfunction PageToolbar() {\n  const scrollToRow = useRemotePublisher(scrollToRow$, TABLE_ENGINE_ID)\n\n  return (\n    <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant=\"outline\">\n      Top\n    </Button>\n  )\n}\n\nfunction InventoryTable({ rows }) {\n  return (\n    <DataTable model={model} engineId={TABLE_ENGINE_ID} style={{ height: 420 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\nUse a unique ID per table on a given screen (`inventory-table`, `transactions-table`). The registry holds one engine per ID, so a second table mounting with the same `engineId` silently overwrites the first — and when the first unmounts, the registry entry is cleared out from under the second. The symptoms are confusing; the prevention is trivial.\n\n## Firing model actions from external UI\n\nFiltering, sorting, grouping, and search often live in a toolbar beside the table. Keep that toolbar connected to the model through the table engine: publish the user action with `dispatchModelAction$`, then read `modelActionState$` to decide which control is active. The toolbar does not need the model handle, and it does not need to keep a separate React state value in sync with the model.\n\n```tsx live\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport {\n  dispatchModelAction$,\n  localModel,\n  modelActionState$,\n  useEngineRef,\n  useRemoteCellValue,\n  useRemotePublisher,\n} from '@virtuoso.dev/data-table'\nimport type { EngineRef } from '@virtuoso.dev/data-table'\n\ntype StatusFilter = 'all' | 'active' | 'paused'\n\nconst rows = [\n  { id: 'acct-1', account: 'Northwind', status: 'active' },\n  { id: 'acct-2', account: 'Contoso', status: 'paused' },\n  { id: 'acct-3', account: 'Fabrikam', status: 'active' },\n]\n\nfunction filterRows(data: typeof rows, status: StatusFilter) {\n  return status === 'all' ? data : data.filter((row) => row.status === status)\n}\n\nfunction StatusControls({ engineRef }: { engineRef: EngineRef }) {\n  const dispatch = useRemotePublisher(dispatchModelAction$, engineRef)\n  const actionState = useRemoteCellValue(modelActionState$, engineRef)\n  const selected = (actionState?.status?.payload ?? 'all') as StatusFilter\n\n  const options: { value: StatusFilter; label: string }[] = [\n    { value: 'all', label: 'All' },\n    { value: 'active', label: 'Active' },\n    { value: 'paused', label: 'Paused' },\n  ]\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {options.map((option) => (\n        <Button\n          aria-pressed={selected === option.value}\n          key={option.value}\n          onClick={() => dispatch({ action: 'status', payload: option.value })}\n          variant={selected === option.value ? 'default' : 'outline'}\n        >\n          {option.label}\n        </Button>\n      ))}\n    </div>\n  )\n}\n\nexport default function App() {\n  const engineRef = useEngineRef()\n  const [model] = useState(() =>\n    localModel({\n      data: rows,\n      pipeline: ['status'],\n      initialActions: [{ action: 'status', payload: 'all' }],\n      actions: {\n        status: {\n          stage: 'status',\n          handler: ({ data, payload }) => filterRows(data, payload as StatusFilter),\n        },\n      },\n    })\n  )\n\n  return (\n    <div className=\"space-y-4\">\n      <StatusControls engineRef={engineRef} />\n      <DataTable className=\"rounded-xl\" computeRowKey={({ data }) => data.id} engineRef={engineRef} model={model} style={{ height: 280 }}>\n        <DataTableColumn field=\"account\">\n          <DataTableColumnHeader>Account</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n        <DataTableColumn field=\"status\">\n          <DataTableColumnHeader>Status</DataTableColumnHeader>\n          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n        </DataTableColumn>\n      </DataTable>\n    </div>\n  )\n}\n```\n\n`modelActionState$` tracks stateful model actions: the current filter, sort, group, search, or other action payload that represents a user choice. Command-like actions are different. `refresh`, `cancel`, `loadMore`, source mutators, and similar commands can be dispatched through `dispatchModelAction$`, but they are not remembered in `modelActionState$` because they do not describe the current toolbar state.\n\nThe bridge blocks only model lifecycle names owned by the table itself: `handshake`, `disconnect`, and `viewportChange`.\n\n## Common state and actions\n\nThe cells and streams most external UI reaches for, grouped by the hook they pair with.\n\nRead with `useRemoteCellValue`:\n\n- `columns$`, `visibleColumns$`, `columnWidths$` — column shape and layout\n- `scrollLocation$`, `scrollerElement$`, `itemHeight$` — scroll state and DOM references\n\nPublish with `useRemotePublisher`:\n\n- `scrollToRow$`, `scrollIntoView$`, `cancelSmoothScroll$` — scroll commands\n- `setColumnSticky$` — pin a column at runtime\n\nFeature modules contribute their own: `resizeColumn$` and friends from `@virtuoso.dev/data-table/column-resize`, `reorderColumns$` from `column-reorder`, `setColumnVisibility$` from `column-visibility`. Each feature page documents the cells and streams it adds.\n\nInside the table — in a cell renderer, in a custom row component — use the local hooks instead: `useVirtuosoLocation()`, `useCurrentlyRenderedData()`, `useVirtuosoLoadingState()`. They read the same underlying state but resolve the engine from React context, so no ref or ID is needed.\n\nFor DOM-level integrations, prefer `scrollerElement$` and `itemHeight$` over `document.querySelector`. The cells stay in sync with React's mount and unmount; a query selector can return a node from a torn-down render or miss a remount entirely.\n\n## Scroll lifecycle props\n\nOne-way notifications and first-render positioning live on the component, not the engine:\n\n- `initialLocation` — scrolls to a row when the table first renders with data\n- `onScroll` — receives the current `ListScrollLocation`\n- `onRenderedDataChange` — receives the rendered data rows, excluding group rows\n\n```tsx\n<DataTable\n  model={model}\n  initialLocation={{ index: 80, align: 'center' }}\n  onRenderedDataChange={(renderedRows) => {\n    console.log(renderedRows.map((row) => row.id))\n  }}\n  onScroll={(location) => {\n    console.log(location.scrollTop)\n  }}\n>\n  {/* columns */}\n</DataTable>\n```\n\nUse props when the parent only needs to observe — log scroll position, fire analytics, set an initial location once. Reach for the remote hooks when UI elsewhere in the tree needs to subscribe to the same updates or push commands back; props can't carry a subscription out to a sibling component.\n\n## Overscan controls\n\nA pair of static-tuning props that, like the scroll lifecycle props above, live on the component rather than on the engine — overscan is a per-table policy, not something external UI flips at runtime. The defaults are good for typical row heights and cell complexity; raise them only when you can see the table struggling.\n\n- `increaseViewportBy` — extra vertical pixels rendered before and after the viewport. Raise when rows contain images that need to warm up before they scroll into view, or when each row's render cost is high enough that the user can outrun the scheduler.\n- `columnOverscanCount` — extra columns rendered on each side of the horizontal viewport. Raise on wide tables that feel jumpy during horizontal scroll, especially when cells contain charts or non-trivial markup.\n\nBoth numbers cost render work even when the user isn't scrolling, so bump them in small steps and stop as soon as the symptom is gone.\n\n<!-- File: packages/data-table/docs/7.customization/01.styling.md (Tokens: ~1218 | Category: anthropic-skill) -->\n---\ntitle: Styling Cells, Headers, and Rows\ndescription: Apply CSS to the visible parts of the table — cell content, header content, and the wrapper around them.\nsidebar:\n  label: Styling\n---\n\nThe visible parts of the table — cells, headers, the outer frame — all accept Tailwind classes the way any component in your app does. Reach for column-level styling when a single column needs to look different (a status pill, a right-aligned number, a muted secondary line) and wrapper-level styling when the change applies to the whole table.\n\n## Column-level styling\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst rows = [\n  { name: 'Standing Desk', status: 'Launch ready', theme: 'Office' },\n  { name: 'USB-C Dock', status: 'Low stock', theme: 'Peripherals' },\n  { name: 'Noise-canceling Headset', status: 'Backorder', theme: 'Audio' },\n]\n\nconst model = localModel({ data: rows })\n\nexport default function App() {\n  return (\n    <DataTable model={model} style={{ height: 280 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"theme\">\n        <DataTableColumnHeader>Theme</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => <span className=\"rounded-full bg-slate-100 px-2 py-1 text-xs font-medium\">{String(cellValue)}</span>}\n        </DataTableCell>\n      </DataTableColumn>\n      <DataTableColumn field=\"status\">\n        <DataTableColumnHeader>Status</DataTableColumnHeader>\n        <DataTableCell>\n          {({ cellValue }) => (\n            <span\n              className={\n                cellValue === 'Backorder'\n                  ? 'font-medium text-red-600'\n                  : cellValue === 'Low stock'\n                    ? 'font-medium text-amber-600'\n                    : 'font-medium text-emerald-600'\n              }\n            >\n              {String(cellValue)}\n            </span>\n          )}\n        </DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n`className` on `DataTableCell` styles the positioned cell. A column header has two widths, so it exposes two styling surfaces:\n\n- `DataTableColumnHeader.className` styles the intrinsic header content and sets the column's base width.\n- `DataTableColumnHeader.containerProps` styles the full rendered column width and accepts event handlers and ARIA attributes.\n\nThe table measures the intrinsic surface before it distributes spare width through `grow`. Measuring the rendered surface instead would feed the grown width back in as the next base width, so the column could not shrink with its viewport. `containerProps` provides a separate full-width paint and interaction surface without changing that measurement.\n\nUse `containerProps` when a background, hover state, outline, box shadow, cursor, or click target must cover the spare width:\n\n```tsx\n<DataTableColumn field=\"name\" grow={1}>\n  <DataTableColumnHeader className=\"min-w-64\" containerProps={{ className: 'cursor-pointer hover:bg-muted/50' }}>\n    Product\n  </DataTableColumnHeader>\n  <DataTableCell className=\"font-medium\">{({ cellValue }) => String(cellValue)}</DataTableCell>\n</DataTableColumn>\n```\n\nKeep layout on `className`. The table owns the outer container's geometry, so width, flex, margin, padding, border, display, and `justify-*` styles on `containerProps` can clip content or move edge-aligned slots. Use `box-shadow` or `outline` instead of a border on that surface, and use `HeaderEnd` for controls that must align to the rendered edge.\n\nContainer events receive bubbled events from nested controls and header slots. If the header also contains sorting, resizing, reordering, links, or buttons, stop propagation in those controls or guard the container handler before running the whole-header action.\n\nRender-function children still let you wrap only the value when it needs its own markup, such as a badge or two-line label.\n\n## Wrapper-level styling\n\n`className` on `<DataTable>` reaches the outer frame around the scroll viewport. The shadcn wrapper already provides the default frame (`rounded-md border`) and table surface, so you do not need to repeat `rounded-md border` at each instance. Use wrapper classes for intentional frame overrides, shadows, and table-level CSS variable overrides.\n\n```tsx\n<DataTable className=\"rounded-xl shadow-sm\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nTo remove or strengthen the default frame, override it directly:\n\n```tsx\n<DataTable className=\"border-0\" model={model}>\n  {/* columns */}\n</DataTable>\n\n<DataTable className=\"border-2\" model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nFor pixel sizing, prefer `style` (`style={{ height: 360 }}`) — the table needs a measurable height to virtualize, and Tailwind's `h-*` utilities work too, as long as the resolved height is real (not a percentage of an unmeasured parent).\n\n## What this page doesn't cover\n\nTwo adjacent concerns have their own pages:\n\n- **Replacing the table's structural components** (Row, GroupHeader, sticky wrappers, scroll element) lives in [Replacing Internals](/data-table/customization/replacing-internals/). That's the `components` prop pattern.\n- **Default classes baked into the shadcn wrapper** — change those by editing `@/components/ui/data-table` directly. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/) for the wrapper anatomy.\n\n<!-- File: packages/data-table/docs/7.customization/02.replacing-internals.md (Tokens: ~1043 | Category: anthropic-skill) -->\n---\ntitle: Replacing Internals\ndescription: Swap out the structural components the table renders — rows, sticky wrappers, scroll element — without forking the wrapper.\nsidebar:\n  label: Replacing Internals\n---\n\nMost styling you do to a table is column-level — a class on a cell, a renderer that builds a status pill. But some changes apply across every row at once: hover states, density, dividers, the markup around sticky columns, the element that actually scrolls. Those changes live in the table's structural slots, and you change them by passing a replacement component.\n\nThe `components` prop is the channel. Each key in `components` swaps out one piece of the table's internal markup with your own component.\n\n```tsx\nimport * as React from 'react'\nimport type { RowComponentProps } from '@virtuoso.dev/data-table'\n\nconst components = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b transition-colors hover:bg-muted/50\" {...props} />\n  }),\n}\n\n;<DataTable components={components} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\nThe override receives the same props the default `<div>` would have received — class, style, children, data attributes, the table's `context` value. Forward them all, or the table loses track of the element. The forwarded `ref` is what the virtualizer reads sizes from, so passing it to the root element is mandatory; the table will appear broken without it.\n\n## What you can replace\n\nThe `components` prop covers structural slots that appear repeatedly or wrap whole regions:\n\n- `Row` — the element used for data rows (one per visible row). Hover states, dividers, alternating backgrounds, density, row-level transitions.\n- `StickyHeader` — the wrapper around the sticky header row. Backgrounds and borders that need to match your page chrome instead of the default.\n- `StickyColumnContainer` — the wrapper around each sticky column region, used in both header and body. The `data-sticky` attribute on the props tells you whether it's the left or right side.\n- `LoadingPlaceholder`, `LoadingOverlay`, `LoadingFooter` — the three loading-state slots. Covered separately in [Empty and Loading States](/data-table/customization/empty-and-loading-states/) because each one has its own narrative.\n\n```tsx\nimport type { DataTableComponents, RowComponentProps, StickyColumnContainerComponentProps } from '@virtuoso.dev/data-table'\n\nconst components: DataTableComponents = {\n  Row: React.forwardRef<HTMLDivElement, RowComponentProps>(function Row(props, ref) {\n    return <div ref={ref} className=\"border-b\" {...props} />\n  }),\n  StickyColumnContainer: React.forwardRef<HTMLDivElement, StickyColumnContainerComponentProps>(function Sticky(props, ref) {\n    const side = props['data-sticky']\n    return <div ref={ref} className={side === 'left' ? 'bg-card shadow-[1px_0_0_0_hsl(var(--border))]' : 'bg-card'} {...props} />\n  }),\n}\n```\n\n## Two slots that aren't in `components`\n\nA few replacements have their own top-level props instead of living under `components`:\n\n- `EmptyPlaceholder` — rendered when the model has zero rows. Top-level prop because most apps customize it and want a single explicit place to look.\n- `ScrollElement` — the element that actually scrolls. Top-level prop because it interacts with viewport ownership (`useWindowScroll`, `customScrollParent`) — see [Scroll Containers](/data-table/customization/scroll-containers/).\n\nBoth follow the same forward-the-props rule.\n\n```tsx\nconst ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {\n  return <div ref={ref} className=\"custom-scrollbar overflow-auto\" {...props} />\n})\n\n;<DataTable EmptyPlaceholder={EmptyPlaceholder} ScrollElement={ScrollElement} model={model}>\n  {/* columns */}\n</DataTable>\n```\n\n## When to edit the wrapper instead\n\nThe shadcn wrapper at `@/components/ui/data-table` already supplies defaults for every slot. Two questions decide where your override belongs:\n\n- **Does every table in your app want this change?** Edit the default in the wrapper file. See [The shadcn Wrapper](/data-table/customization/shadcn-wrapper/).\n- **Does this one table want something different?** Pass `components` (or `EmptyPlaceholder` / `ScrollElement`) on that table only.\n\nBoth routes hit the same slots; the difference is scope.\n\n## Don't reach for `data-testid` as a styling hook\n\nPer the data-table conventions, `data-testid` is reserved for tests. When your override needs a styling hook, use a semantic data attribute (`data-density=\"compact\"`) or a className — not a test ID. The table itself never styles against `data-testid` and you shouldn't either.\n\n<!-- File: packages/data-table/docs/7.customization/03.empty-and-loading-states.md (Tokens: ~815 | Category: anthropic-skill) -->\n---\ntitle: Empty, Loading, and Error States\ndescription: Customize empty placeholders and remote loading states without changing data model.\nsidebar:\n  label: Empty and Loading States\n---\n\nThe table has three \"no rows\" states: it has zero rows to display (empty), it's loading rows that haven't arrived yet (loading), or a load failed (error). Each state has its own slot component, and the shadcn wrapper ships sensible defaults for all of them — override a slot when the default copy doesn't fit your domain.\n\n## Empty placeholder\n\n`EmptyPlaceholder` renders when the model has zero rows. The component receives the table `context` prop.\n\n```tsx live\nimport { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'\nimport { localModel } from '@virtuoso.dev/data-table'\n\nconst model = localModel<{ name: string }>({ data: [] })\n\nfunction EmptyState({ context }: { context: { query: string } }) {\n  return <div className=\"p-6 text-sm text-muted-foreground\">No products match \"{context.query}\".</div>\n}\n\nexport default function App() {\n  return (\n    <DataTable EmptyPlaceholder={EmptyState} className=\"rounded-xl\" context={{ query: 'archived' }} model={model} style={{ height: 240 }}>\n      <DataTableColumn field=\"name\">\n        <DataTableColumnHeader>Product</DataTableColumnHeader>\n        <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>\n      </DataTableColumn>\n    </DataTable>\n  )\n}\n```\n\n## Remote loading and error state\n\n`remoteModel()` drives three slot components:\n\n- `LoadingPlaceholder` — `initial` phase (first unresolved dataset)\n- `LoadingOverlay` — `refresh` phase (request params changed, old rows still visible)\n- `LoadingFooter` — `end` phase (append mode is fetching more rows below)\n\nOffset viewport fetches show up as placeholder rows, not a table-level loading slot. `DataTableLoadingState` exposes a `start` segment for custom integrations, but the built-in `remoteModel()` never sets it.\n\nReplace any slot through `components`:\n\n```tsx\nfunction LoadingOverlay({ loadingState }: LoadingComponentProps) {\n  if (loadingState.refresh.status === 'error') {\n    return <div role=\"status\">Refresh failed: {loadingState.refresh.errorMessage}</div>\n  }\n\n  return <div role=\"status\">Refreshing rows...</div>\n}\n\n;<DataTable components={{ LoadingOverlay }} model={model} />\n```\n\nThe shadcn wrapper ships defaults for all three slots. Override them for different copy, skeletons, or error recovery controls.\n\nExternal UI reads the same state through `useVirtuosoLoadingState()` (inside the table) or `loadingState$` with remote engine hooks (outside).\n\n## Error recovery\n\nErrors attach to the phase that failed, so a first-load failure can be handled differently from a refresh failure:\n\n```tsx\nfunction LoadingPlaceholder({ loadingState }: LoadingComponentProps) {\n  if (loadingState.initial.status === 'error') {\n    return (\n      <div className=\"p-6 text-sm\" role=\"status\">\n        Could not load products. Check your connection and try again.\n      </div>\n    )\n  }\n\n  return <div className=\"p-6 text-sm text-muted-foreground\">Loading products...</div>\n}\n\nfunction LoadingFooter({ loadingState }: LoadingComponentProps) {\n  if (loadingState.end.status === 'error') {\n    return <div role=\"status\">Could not load more rows.</div>\n  }\n\n  return loadingState.end.status === 'loading' ? <div>Loading more...</div> : null\n}\n\n;<DataTable components={{ LoadingFooter, LoadingPlaceholder }} model={model} />\n```\n\nRetry buttons belong with the slot that's showing the error: initial failure → retry from the placeholder, refresh failure → retry from the overlay, append failure → retry from the footer.\n\n<!-- ... +491 more modular skills available via /api/skills/petyosi/react-virtuoso?path={path} -->\n</yakaai_skills>"}