[{"data":1,"prerenderedAt":1539},["ShallowReactive",2],{"navigation":3,"$f2vxmxe0ttlw1b":470,"\u002Fdocs\u002Fcomponents\u002Fdata-table":481,"surround-\u002Fdocs\u002Fcomponents\u002Fdata-table":1536},[4],{"title":5,"path":6,"stem":7,"children":8,"page":18},"Docs","\u002Fdocs","docs",[9,298,324,342,348,365,391,405],{"title":10,"path":11,"stem":12,"children":13,"new":18,"type":297},"Components","\u002Fdocs\u002Fcomponents","docs\u002F02.components",[14,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,205,209,213,217,221,225,229,233,237,241,245,249,253,257,261,265,269,273,277,281,285,289,293],{"title":15,"path":16,"stem":17,"new":18,"type":19,"children":-1},"Accordion","\u002Fdocs\u002Fcomponents\u002Faccordion","docs\u002Fcomponents\u002Faccordion",false,"component",{"title":21,"path":22,"stem":23,"new":18,"type":19,"children":-1},"Alert","\u002Fdocs\u002Fcomponents\u002Falert","docs\u002Fcomponents\u002Falert",{"title":25,"path":26,"stem":27,"new":18,"type":19,"children":-1},"Alert Dialog","\u002Fdocs\u002Fcomponents\u002Falert-dialog","docs\u002Fcomponents\u002Falert-dialog",{"title":29,"path":30,"stem":31,"new":18,"type":19,"children":-1},"Aspect Ratio","\u002Fdocs\u002Fcomponents\u002Faspect-ratio","docs\u002Fcomponents\u002Faspect-ratio",{"title":33,"path":34,"stem":35,"new":18,"type":19,"children":-1},"Attachment","\u002Fdocs\u002Fcomponents\u002Fattachment","docs\u002Fcomponents\u002Fattachment",{"title":37,"path":38,"stem":39,"new":18,"type":19,"children":-1},"Avatar","\u002Fdocs\u002Fcomponents\u002Favatar","docs\u002Fcomponents\u002Favatar",{"title":41,"path":42,"stem":43,"new":18,"type":19,"children":-1},"Badge","\u002Fdocs\u002Fcomponents\u002Fbadge","docs\u002Fcomponents\u002Fbadge",{"title":45,"path":46,"stem":47,"new":18,"type":19,"children":-1},"Breadcrumb","\u002Fdocs\u002Fcomponents\u002Fbreadcrumb","docs\u002Fcomponents\u002Fbreadcrumb",{"title":49,"path":50,"stem":51,"new":18,"type":19,"children":-1},"Bubble","\u002Fdocs\u002Fcomponents\u002Fbubble","docs\u002Fcomponents\u002Fbubble",{"title":53,"path":54,"stem":55,"new":18,"type":19,"children":-1},"Button","\u002Fdocs\u002Fcomponents\u002Fbutton","docs\u002Fcomponents\u002Fbutton",{"title":57,"path":58,"stem":59,"new":18,"type":19,"children":-1},"Button Group","\u002Fdocs\u002Fcomponents\u002Fbutton-group","docs\u002Fcomponents\u002Fbutton-group",{"title":61,"path":62,"stem":63,"new":18,"type":19,"children":-1},"Calendar","\u002Fdocs\u002Fcomponents\u002Fcalendar","docs\u002Fcomponents\u002Fcalendar",{"title":65,"path":66,"stem":67,"new":18,"type":19,"children":-1},"Card","\u002Fdocs\u002Fcomponents\u002Fcard","docs\u002Fcomponents\u002Fcard",{"title":69,"path":70,"stem":71,"new":18,"type":19,"children":-1},"Carousel","\u002Fdocs\u002Fcomponents\u002Fcarousel","docs\u002Fcomponents\u002Fcarousel",{"title":73,"path":74,"stem":75,"new":18,"type":19,"children":-1},"Chart","\u002Fdocs\u002Fcomponents\u002Fchart","docs\u002Fcomponents\u002Fchart",{"title":77,"path":78,"stem":79,"new":18,"type":19,"children":-1},"Checkbox","\u002Fdocs\u002Fcomponents\u002Fcheckbox","docs\u002Fcomponents\u002Fcheckbox",{"title":81,"path":82,"stem":83,"new":18,"type":19,"children":-1},"Collapsible","\u002Fdocs\u002Fcomponents\u002Fcollapsible","docs\u002Fcomponents\u002Fcollapsible",{"title":85,"path":86,"stem":87,"new":18,"type":19,"children":-1},"Combobox","\u002Fdocs\u002Fcomponents\u002Fcombobox","docs\u002Fcomponents\u002Fcombobox",{"title":89,"path":90,"stem":91,"new":18,"type":19,"children":-1},"Command","\u002Fdocs\u002Fcomponents\u002Fcommand","docs\u002Fcomponents\u002Fcommand",{"title":93,"path":94,"stem":95,"new":18,"type":19,"children":-1},"Context Menu","\u002Fdocs\u002Fcomponents\u002Fcontext-menu","docs\u002Fcomponents\u002Fcontext-menu",{"title":97,"path":98,"stem":99,"new":18,"type":19,"children":-1},"Data Table","\u002Fdocs\u002Fcomponents\u002Fdata-table","docs\u002Fcomponents\u002Fdata-table",{"title":101,"path":102,"stem":103,"new":18,"type":19,"children":-1},"Date Picker","\u002Fdocs\u002Fcomponents\u002Fdate-picker","docs\u002Fcomponents\u002Fdate-picker",{"title":105,"path":106,"stem":107,"new":18,"type":19,"children":-1},"Dialog","\u002Fdocs\u002Fcomponents\u002Fdialog","docs\u002Fcomponents\u002Fdialog",{"title":109,"path":110,"stem":111,"new":18,"type":19,"children":-1},"Drawer","\u002Fdocs\u002Fcomponents\u002Fdrawer","docs\u002Fcomponents\u002Fdrawer",{"title":113,"path":114,"stem":115,"new":18,"type":19,"children":-1},"Dropdown Menu","\u002Fdocs\u002Fcomponents\u002Fdropdown-menu","docs\u002Fcomponents\u002Fdropdown-menu",{"title":117,"path":118,"stem":119,"new":18,"type":19,"children":-1},"Empty","\u002Fdocs\u002Fcomponents\u002Fempty","docs\u002Fcomponents\u002Fempty",{"title":121,"path":122,"stem":123,"new":18,"type":19,"children":-1},"Field","\u002Fdocs\u002Fcomponents\u002Ffield","docs\u002Fcomponents\u002Ffield",{"title":125,"path":126,"stem":127,"new":18,"type":19,"children":-1},"Form","\u002Fdocs\u002Fcomponents\u002Fform","docs\u002Fcomponents\u002Fform",{"title":129,"path":130,"stem":131,"new":18,"type":19,"children":-1},"Hover Card","\u002Fdocs\u002Fcomponents\u002Fhover-card","docs\u002Fcomponents\u002Fhover-card",{"title":133,"path":134,"stem":135,"new":18,"type":19,"children":-1},"Input","\u002Fdocs\u002Fcomponents\u002Finput","docs\u002Fcomponents\u002Finput",{"title":137,"path":138,"stem":139,"new":18,"type":19,"children":-1},"Input Group","\u002Fdocs\u002Fcomponents\u002Finput-group","docs\u002Fcomponents\u002Finput-group",{"title":141,"path":142,"stem":143,"new":18,"type":19,"children":-1},"Input OTP","\u002Fdocs\u002Fcomponents\u002Finput-otp","docs\u002Fcomponents\u002Finput-otp",{"title":145,"path":146,"stem":147,"new":18,"type":19,"children":-1},"Item","\u002Fdocs\u002Fcomponents\u002Fitem","docs\u002Fcomponents\u002Fitem",{"title":149,"path":150,"stem":151,"new":18,"type":19,"children":-1},"Kbd","\u002Fdocs\u002Fcomponents\u002Fkbd","docs\u002Fcomponents\u002Fkbd",{"title":153,"path":154,"stem":155,"new":18,"type":19,"children":-1},"Label","\u002Fdocs\u002Fcomponents\u002Flabel","docs\u002Fcomponents\u002Flabel",{"title":157,"path":158,"stem":159,"new":18,"type":19,"children":-1},"Marker","\u002Fdocs\u002Fcomponents\u002Fmarker","docs\u002Fcomponents\u002Fmarker",{"title":161,"path":162,"stem":163,"new":18,"type":19,"children":-1},"Menubar","\u002Fdocs\u002Fcomponents\u002Fmenubar","docs\u002Fcomponents\u002Fmenubar",{"title":165,"path":166,"stem":167,"new":18,"type":19,"children":-1},"Message","\u002Fdocs\u002Fcomponents\u002Fmessage","docs\u002Fcomponents\u002Fmessage",{"title":169,"path":170,"stem":171,"new":18,"type":19,"children":-1},"Message Scroller","\u002Fdocs\u002Fcomponents\u002Fmessage-scroller","docs\u002Fcomponents\u002Fmessage-scroller",{"title":173,"path":174,"stem":175,"new":18,"type":19,"children":-1},"Native Select","\u002Fdocs\u002Fcomponents\u002Fnative-select","docs\u002Fcomponents\u002Fnative-select",{"title":177,"path":178,"stem":179,"new":18,"type":19,"children":-1},"Navigation Menu","\u002Fdocs\u002Fcomponents\u002Fnavigation-menu","docs\u002Fcomponents\u002Fnavigation-menu",{"title":181,"path":182,"stem":183,"new":18,"type":19,"children":-1},"Number Field","\u002Fdocs\u002Fcomponents\u002Fnumber-field","docs\u002Fcomponents\u002Fnumber-field",{"title":185,"path":186,"stem":187,"new":18,"type":19,"children":-1},"Pagination","\u002Fdocs\u002Fcomponents\u002Fpagination","docs\u002Fcomponents\u002Fpagination",{"title":189,"path":190,"stem":191,"new":18,"type":19,"children":-1},"Pin Input","\u002Fdocs\u002Fcomponents\u002Fpin-input","docs\u002Fcomponents\u002Fpin-input",{"title":193,"path":194,"stem":195,"new":18,"type":19,"children":-1},"Popover","\u002Fdocs\u002Fcomponents\u002Fpopover","docs\u002Fcomponents\u002Fpopover",{"title":197,"path":198,"stem":199,"new":18,"type":19,"children":-1},"Progress","\u002Fdocs\u002Fcomponents\u002Fprogress","docs\u002Fcomponents\u002Fprogress",{"title":201,"path":202,"stem":203,"new":204,"type":19,"children":-1},"Questionnaire","\u002Fdocs\u002Fcomponents\u002Fquestionnaire","docs\u002Fcomponents\u002Fquestionnaire",true,{"title":206,"path":207,"stem":208,"new":18,"type":19,"children":-1},"Radio Group","\u002Fdocs\u002Fcomponents\u002Fradio-group","docs\u002Fcomponents\u002Fradio-group",{"title":210,"path":211,"stem":212,"new":18,"type":19,"children":-1},"Range Calendar","\u002Fdocs\u002Fcomponents\u002Frange-calendar","docs\u002Fcomponents\u002Frange-calendar",{"title":214,"path":215,"stem":216,"new":18,"type":19,"children":-1},"Resizable","\u002Fdocs\u002Fcomponents\u002Fresizable","docs\u002Fcomponents\u002Fresizable",{"title":218,"path":219,"stem":220,"new":18,"type":19,"children":-1},"Scroll Area","\u002Fdocs\u002Fcomponents\u002Fscroll-area","docs\u002Fcomponents\u002Fscroll-area",{"title":222,"path":223,"stem":224,"new":18,"type":19,"children":-1},"Select","\u002Fdocs\u002Fcomponents\u002Fselect","docs\u002Fcomponents\u002Fselect",{"title":226,"path":227,"stem":228,"new":18,"type":19,"children":-1},"Separator","\u002Fdocs\u002Fcomponents\u002Fseparator","docs\u002Fcomponents\u002Fseparator",{"title":230,"path":231,"stem":232,"new":18,"type":19,"children":-1},"Sheet","\u002Fdocs\u002Fcomponents\u002Fsheet","docs\u002Fcomponents\u002Fsheet",{"title":234,"path":235,"stem":236,"new":18,"type":19,"children":-1},"Sidebar","\u002Fdocs\u002Fcomponents\u002Fsidebar","docs\u002Fcomponents\u002Fsidebar",{"title":238,"path":239,"stem":240,"new":18,"type":19,"children":-1},"Skeleton","\u002Fdocs\u002Fcomponents\u002Fskeleton","docs\u002Fcomponents\u002Fskeleton",{"title":242,"path":243,"stem":244,"new":18,"type":19,"children":-1},"Slider","\u002Fdocs\u002Fcomponents\u002Fslider","docs\u002Fcomponents\u002Fslider",{"title":246,"path":247,"stem":248,"new":18,"type":19,"children":-1},"Sonner","\u002Fdocs\u002Fcomponents\u002Fsonner","docs\u002Fcomponents\u002Fsonner",{"title":250,"path":251,"stem":252,"new":18,"type":19,"children":-1},"Spinner","\u002Fdocs\u002Fcomponents\u002Fspinner","docs\u002Fcomponents\u002Fspinner",{"title":254,"path":255,"stem":256,"new":18,"type":19,"children":-1},"Stepper","\u002Fdocs\u002Fcomponents\u002Fstepper","docs\u002Fcomponents\u002Fstepper",{"title":258,"path":259,"stem":260,"new":18,"type":19,"children":-1},"Switch","\u002Fdocs\u002Fcomponents\u002Fswitch","docs\u002Fcomponents\u002Fswitch",{"title":262,"path":263,"stem":264,"new":18,"type":19,"children":-1},"Table","\u002Fdocs\u002Fcomponents\u002Ftable","docs\u002Fcomponents\u002Ftable",{"title":266,"path":267,"stem":268,"new":18,"type":19,"children":-1},"Tabs","\u002Fdocs\u002Fcomponents\u002Ftabs","docs\u002Fcomponents\u002Ftabs",{"title":270,"path":271,"stem":272,"new":18,"type":19,"children":-1},"Tags Input","\u002Fdocs\u002Fcomponents\u002Ftags-input","docs\u002Fcomponents\u002Ftags-input",{"title":274,"path":275,"stem":276,"new":18,"type":19,"children":-1},"Textarea","\u002Fdocs\u002Fcomponents\u002Ftextarea","docs\u002Fcomponents\u002Ftextarea",{"title":278,"path":279,"stem":280,"new":18,"type":19,"children":-1},"Toast","\u002Fdocs\u002Fcomponents\u002Ftoast","docs\u002Fcomponents\u002Ftoast",{"title":282,"path":283,"stem":284,"new":18,"type":19,"children":-1},"Toggle","\u002Fdocs\u002Fcomponents\u002Ftoggle","docs\u002Fcomponents\u002Ftoggle",{"title":286,"path":287,"stem":288,"new":18,"type":19,"children":-1},"Toggle Group","\u002Fdocs\u002Fcomponents\u002Ftoggle-group","docs\u002Fcomponents\u002Ftoggle-group",{"title":290,"path":291,"stem":292,"new":18,"type":19,"children":-1},"Tooltip","\u002Fdocs\u002Fcomponents\u002Ftooltip","docs\u002Fcomponents\u002Ftooltip",{"title":294,"path":295,"stem":296,"new":18,"type":19,"children":-1},"Typography","\u002Fdocs\u002Fcomponents\u002Ftypography","docs\u002Fcomponents\u002Ftypography","group",{"title":299,"path":300,"stem":301,"children":302,"new":18,"type":297},"Installation","\u002Fdocs\u002Finstallation","docs\u002F02.installation",[303,308,312,316,320],{"title":304,"path":305,"stem":306,"new":18,"type":307,"children":-1},"Vite","\u002Fdocs\u002Finstallation\u002Fvite","docs\u002Finstallation\u002F01.vite","page",{"title":309,"path":310,"stem":311,"new":18,"type":307,"children":-1},"Nuxt","\u002Fdocs\u002Finstallation\u002Fnuxt","docs\u002Finstallation\u002F02.nuxt",{"title":313,"path":314,"stem":315,"new":18,"type":307,"children":-1},"Astro","\u002Fdocs\u002Finstallation\u002Fastro","docs\u002Finstallation\u002F03.astro",{"title":317,"path":318,"stem":319,"new":18,"type":307,"children":-1},"Laravel","\u002Fdocs\u002Finstallation\u002Flaravel","docs\u002Finstallation\u002F04.laravel",{"title":321,"path":322,"stem":323,"new":18,"type":307,"children":-1},"Manual Installation","\u002Fdocs\u002Finstallation\u002Fmanual","docs\u002Finstallation\u002F05.manual",{"title":325,"path":326,"stem":327,"children":328,"new":18,"type":297},"Dark Mode","\u002Fdocs\u002Fdark-mode","docs\u002F05.dark-mode",[329,332,335,339],{"title":304,"path":330,"stem":331,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fvite","docs\u002Fdark-mode\u002F01.vite",{"title":309,"path":333,"stem":334,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fnuxt","docs\u002Fdark-mode\u002F02.nuxt",{"title":336,"path":337,"stem":338,"new":18,"type":307,"children":-1},"Vitepress","\u002Fdocs\u002Fdark-mode\u002Fvitepress","docs\u002Fdark-mode\u002F03.vitepress",{"title":313,"path":340,"stem":341,"new":18,"type":307,"children":-1},"\u002Fdocs\u002Fdark-mode\u002Fastro","docs\u002Fdark-mode\u002F04.astro",{"title":343,"path":344,"stem":345,"children":346,"new":18,"type":297},"Directory","\u002Fdocs\u002Fdirectory","docs\u002Fdirectory\u002Findex",[347],{"title":343,"path":344,"stem":345,"new":18,"type":307,"children":-1},{"title":349,"path":350,"stem":351,"children":352,"new":18,"type":297},"Forms","\u002Fdocs\u002Fforms","docs\u002Fforms",[353,357,361],{"title":354,"path":355,"stem":356,"new":18,"type":307,"children":-1},"VeeValidate","\u002Fdocs\u002Fforms\u002Fvee-validate","docs\u002Fforms\u002F01.vee-validate",{"title":358,"path":359,"stem":360,"new":18,"type":307,"children":-1},"TanStack Form","\u002Fdocs\u002Fforms\u002Ftanstack-form","docs\u002Fforms\u002F02.tanstack-form",{"title":362,"path":363,"stem":364,"new":18,"type":307,"children":-1},"Formisch","\u002Fdocs\u002Fforms\u002Fformisch","docs\u002Fforms\u002F03.formisch",{"title":366,"path":367,"stem":368,"children":369,"new":18,"type":297},"Registry","\u002Fdocs\u002Fregistry","docs\u002Fregistry\u002Findex",[370,371,375,379,383,387],{"title":366,"path":367,"stem":368,"new":18,"type":307,"children":-1},{"title":372,"path":373,"stem":374,"new":18,"type":307,"children":-1},"Examples","\u002Fdocs\u002Fregistry\u002Fexamples","docs\u002Fregistry\u002Fexamples",{"title":376,"path":377,"stem":378,"new":18,"type":307,"children":-1},"FAQ","\u002Fdocs\u002Fregistry\u002Ffaq","docs\u002Fregistry\u002Ffaq",{"title":380,"path":381,"stem":382,"new":18,"type":307,"children":-1},"Getting Started","\u002Fdocs\u002Fregistry\u002Fgetting-started","docs\u002Fregistry\u002Fgetting-started",{"title":384,"path":385,"stem":386,"new":18,"type":307,"children":-1},"registry-item.json","\u002Fdocs\u002Fregistry\u002Fregistry-item-json","docs\u002Fregistry\u002Fregistry-item-json",{"title":388,"path":389,"stem":390,"new":18,"type":307,"children":-1},"registry.json","\u002Fdocs\u002Fregistry\u002Fregistry-json","docs\u002Fregistry\u002Fregistry-json",{"title":392,"path":393,"stem":394,"children":395,"new":18,"type":297},"Utilities","\u002Fdocs\u002Futilities","docs\u002Futilities\u002Findex",[396,397,401],{"title":392,"path":393,"stem":394,"new":18,"type":307,"children":-1},{"title":398,"path":399,"stem":400,"new":18,"type":307,"children":-1},"scroll-fade","\u002Fdocs\u002Futilities\u002Fscroll-fade","docs\u002Futilities\u002Fscroll-fade",{"title":402,"path":403,"stem":404,"new":18,"type":307,"children":-1},"shimmer","\u002Fdocs\u002Futilities\u002Fshimmer","docs\u002Futilities\u002Fshimmer",{"path":6,"stem":7,"title":406,"type":297,"children":407},"Get Started",[408,412,419,423,427,433,437,441,445,449,453,457,461,465],{"title":409,"path":410,"stem":411,"new":18,"type":307,"children":-1},"Introduction","\u002Fdocs\u002Fintroduction","docs\u002F01.introduction",{"title":299,"path":300,"stem":301,"children":413,"new":18,"type":297},[414,415,416,417,418],{"title":304,"path":305,"stem":306,"new":18,"type":307,"children":-1},{"title":309,"path":310,"stem":311,"new":18,"type":307,"children":-1},{"title":313,"path":314,"stem":315,"new":18,"type":307,"children":-1},{"title":317,"path":318,"stem":319,"new":18,"type":307,"children":-1},{"title":321,"path":322,"stem":323,"new":18,"type":307,"children":-1},{"title":420,"path":421,"stem":422,"new":18,"type":307,"children":-1},"components.json","\u002Fdocs\u002Fcomponents-json","docs\u002F03.components-json",{"title":424,"path":425,"stem":426,"new":18,"type":307,"children":-1},"Theming","\u002Fdocs\u002Ftheming","docs\u002F04.theming",{"title":325,"path":326,"stem":327,"children":428,"new":18,"type":297},[429,430,431,432],{"title":304,"path":330,"stem":331,"new":18,"type":307,"children":-1},{"title":309,"path":333,"stem":334,"new":18,"type":307,"children":-1},{"title":336,"path":337,"stem":338,"new":18,"type":307,"children":-1},{"title":313,"path":340,"stem":341,"new":18,"type":307,"children":-1},{"title":434,"path":435,"stem":436,"new":18,"type":307,"children":-1},"CLI","\u002Fdocs\u002Fcli","docs\u002F06.cli",{"title":438,"path":439,"stem":440,"new":18,"type":307,"children":-1},"JavaScript","\u002Fdocs\u002Fjavascript","docs\u002F07.javascript",{"title":442,"path":443,"stem":444,"new":18,"type":307,"children":-1},"RTL","\u002Fdocs\u002Frtl","docs\u002F08.rtl",{"title":446,"path":447,"stem":448,"new":18,"type":307,"children":-1},"Figma","\u002Fdocs\u002Ffigma","docs\u002F09.figma",{"title":450,"path":451,"stem":452,"new":18,"type":307,"children":-1},"Changelog","\u002Fdocs\u002Fchangelog","docs\u002F10.changelog",{"title":454,"path":455,"stem":456,"new":18,"type":307,"children":-1},"Legacy Docs","\u002Fdocs\u002Flegacy","docs\u002F11.legacy",{"title":458,"path":459,"stem":460,"new":18,"type":307,"children":-1},"Skills","\u002Fdocs\u002Fskills","docs\u002Fskills",{"title":462,"path":463,"stem":464,"new":18,"type":307,"children":-1},"Typeset","\u002Fdocs\u002Ftypeset","docs\u002Ftypeset",{"title":392,"path":393,"stem":394,"children":466,"new":18,"type":297},[467,468,469],{"title":392,"path":393,"stem":394,"new":18,"type":307,"children":-1},{"title":398,"path":399,"stem":400,"new":18,"type":307,"children":-1},{"title":402,"path":403,"stem":404,"new":18,"type":307,"children":-1},{"repo":471},{"id":472,"name":473,"repo":474,"description":475,"createdAt":476,"updatedAt":477,"pushedAt":478,"stars":479,"watchers":479,"forks":479,"defaultBranch":480},1360880822,"v-design","v-stella\u002Fv-design","An open-source Vue design system and component registry.","2026-09-08T03:28:46Z","2026-09-09T08:39:14Z","2026-09-09T08:36:47Z",0,"dev",{"id":482,"title":97,"body":483,"description":1529,"extension":1530,"links":1531,"meta":1532,"navigation":204,"new":18,"path":98,"rawbody":1533,"seo":1534,"stem":99,"__hash__":1535},"content\u002Fdocs\u002Fcomponents\u002Fdata-table.md",{"type":484,"value":485,"toc":1500},"minimark",[486,493,504,508,511,522,525,533,545,549,561,624,627,636,646,659,665,669,672,681,685,688,696,699,741,744,771,782,788,805,808,811,944,948,951],[487,488],"component-preview",{"align":489,"description":490,"name":491,"previewClass":492},"start","A data table with sorting, filtering, and pagination.","DataTableDemo","items-start h-auto px-4 md:px-8",[494,495,500],"vue-school-link",{"className":496,"lesson":498,"placement":499},[497],"mt-6","data-tables-and-sonner-in-shadcn-vue","top",[501,502,503],"p",{},"Watch a Vue School video about data tables in shadcn-vue.",[505,506,409],"h2",{"id":507},"introduction",[501,509,510],{},"Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.",[501,512,513,514,521],{},"It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that ",[515,516,520],"a",{"href":517,"rel":518},"https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fintroduction#what-is-headless-ui",[519],"nofollow","headless UI"," provides.",[501,523,524],{},"So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.",[501,526,527,528,532],{},"We'll start with the basic ",[529,530,531],"code",{},"\u003CTable \u002F>"," component and build a complex data table from scratch.",[534,535,538],"callout",{"className":536},[537],"mt-4",[501,539,540,544],{},[541,542,543],"strong",{},"Tip:"," If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.",[505,546,548],{"id":547},"table-of-contents","Table of Contents",[501,550,551,552,557,558,560],{},"This guide will show you how to use ",[515,553,556],{"href":554,"rel":555},"https:\u002F\u002Ftanstack.com\u002Ftable",[519],"TanStack Table"," and the ",[529,559,531],{}," component to build your own custom data table. We'll cover the following topics:",[562,563,564,571,577,583,588,594,600,606,612,618],"ul",{},[565,566,567],"li",{},[515,568,570],{"href":569},"#set-up-table-features","Set up Table Features",[565,572,573],{},[515,574,576],{"href":575},"#basic-table","Basic Table",[565,578,579],{},[515,580,582],{"href":581},"#row-actions","Row Actions",[565,584,585],{},[515,586,185],{"href":587},"#pagination",[565,589,590],{},[515,591,593],{"href":592},"#sorting","Sorting",[565,595,596],{},[515,597,599],{"href":598},"#filtering","Filtering",[565,601,602],{},[515,603,605],{"href":604},"#visibility","Visibility",[565,607,608],{},[515,609,611],{"href":610},"#row-selection","Row Selection",[565,613,614],{},[515,615,617],{"href":616},"#expanding","Expanding",[565,619,620],{},[515,621,623],{"href":622},"#reusable-components","Reusable Components",[505,625,299],{"id":626},"installation",[628,629,630],"ol",{},[565,631,632,633,635],{},"Add the ",[529,634,531],{}," component to your project:",[637,638,644],"pre",{"className":639,"code":641,"language":642,"meta":643},[640],"language-bash","npx shadcn-vue@latest add table\n","bash","",[529,645,641],{"__ignoreMap":643},[628,647,649],{"start":648},2,[565,650,632,651,654,655,658],{},[529,652,653],{},"@tanstack\u002Fvue-table"," dependency. This guide uses ",[541,656,657],{},"TanStack Table v9",":",[637,660,663],{"className":661,"code":662,"language":642,"meta":643},[640],"npm install @tanstack\u002Fvue-table\n",[529,664,662],{"__ignoreMap":643},[505,666,668],{"id":667},"prerequisites","Prerequisites",[501,670,671],{},"We are going to build a table to show recent payments. Here's what our data looks like:",[637,673,679],{"className":674,"code":676,"language":677,"meta":678},[675],"language-ts","interface Payment {\n  id: string\n  amount: number\n  status: 'pending' | 'processing' | 'success' | 'failed'\n  email: string\n}\n\nexport const payments: Payment[] = [\n  {\n    id: '728ed52f',\n    amount: 100,\n    status: 'pending',\n    email: 'm@example.com',\n  },\n  {\n    id: '489e1d42',\n    amount: 125,\n    status: 'processing',\n    email: 'example@gmail.com',\n  },\n  \u002F\u002F ...\n]\n","ts","showLineNumbers",[529,680,676],{"__ignoreMap":643},[505,682,684],{"id":683},"project-structure","Project Structure",[501,686,687],{},"Start by creating the following file structure:",[637,689,694],{"className":690,"code":692,"language":693,"meta":643},[691],"language-ansi"," components\n    └── payments\n          ├── columns.ts\n          ├── features.ts\n          ├── data-table.vue\n          ├── data-table-dropdown.vue\n└── app.vue\n","ansi",[529,695,692],{"__ignoreMap":643},[501,697,698],{},"I'm using a Nuxt example here but this works for any other Vue framework.",[562,700,701,707,717,727,735],{},[565,702,703,706],{},[529,704,705],{},"columns.ts"," It will contain our column definitions.",[565,708,709,712,713,716],{},[529,710,711],{},"features.ts"," It will contain the shared ",[529,714,715],{},"features"," object that tells TanStack Table which behavior to enable.",[565,718,719,722,723,726],{},[529,720,721],{},"data-table.vue"," It will contain our ",[529,724,725],{},"\u003CDataTable \u002F>"," component.",[565,728,729,722,732,726],{},[529,730,731],{},"data-table-dropdown.vue",[529,733,734],{},"\u003CDropdownAction \u002F>",[565,736,737,740],{},[529,738,739],{},"app.vue"," This is where we'll fetch data and render our table.",[505,742,570],{"id":743},"set-up-table-features",[501,745,746,747,750,751,754,755,758,759,762,763,766,767,770],{},"TanStack Table v9 is feature-based: you opt into the behavior you want — sorting, filtering, pagination, and so on — by declaring it with ",[529,748,749],{},"tableFeatures()",". Anything you don't register is tree-shaken out of your bundle. That includes the built-in filter and sort functions: register the ones your columns rely on under ",[529,752,753],{},"filterFns"," and ",[529,756,757],{},"sortFns",". Our email filter uses ",[529,760,761],{},"includesString",", and string columns sort with ",[529,764,765],{},"alphanumeric"," \u002F ",[529,768,769],{},"text",".",[501,772,773,774,776,777,779,780,726],{},"We'll define the ",[529,775,715],{}," object once in ",[529,778,711],{}," and share it between our column definitions and the ",[529,781,725],{},[637,783,786],{"className":784,"code":785,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Ffeatures.ts\nimport {\n  columnFilteringFeature,\n  columnVisibilityFeature,\n  createExpandedRowModel,\n  createFilteredRowModel,\n  createPaginatedRowModel,\n  createSortedRowModel,\n  filterFn_includesString,\n  rowExpandingFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  sortFn_alphanumeric,\n  sortFn_text,\n  tableFeatures,\n} from '@tanstack\u002Fvue-table'\n\n\u002F\u002F New in v9: declare the features this table uses — anything you don't\n\u002F\u002F register is tree-shaken out of the bundle.\nexport const features = tableFeatures({\n  columnFilteringFeature,\n  columnVisibilityFeature,\n  rowExpandingFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  expandedRowModel: createExpandedRowModel(),\n  filteredRowModel: createFilteredRowModel(),\n  paginatedRowModel: createPaginatedRowModel(),\n  sortedRowModel: createSortedRowModel(),\n  filterFns: { includesString: filterFn_includesString },\n  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },\n})\n\n\u002F\u002F Pass this as the first generic argument to `ColumnDef`, `Column`, `Table`,\n\u002F\u002F and `Row` so each type knows which feature APIs are available.\nexport type DataTableFeatures = typeof features\n",[529,787,785],{"__ignoreMap":643},[534,789,791],{"className":790},[537],[501,792,793,796,797,800,801,804],{},[541,794,795],{},"Note:"," The core row model is always included, so you never register it yourself. Row models for optional features are created with ",[529,798,799],{},"create*RowModel()"," and registered on the features object — there are no more ",[529,802,803],{},"get*RowModel"," table options.",[505,806,576],{"id":807},"basic-table",[501,809,810],{},"Let's start by building a basic table.",[812,813,814,819,825,831,839,845,851,868,876,890,909,931,935,938],"steps",{},[815,816,818],"h3",{"id":817},"column-definitions","Column Definitions",[501,820,821,822,824],{},"First, we'll define our columns in the ",[529,823,705],{}," file using a column helper typed with our features.",[637,826,829],{"className":827,"code":828,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { createColumnHelper } from '@tanstack\u002Fvue-table'\nimport type { DataTableFeatures } from '.\u002Ffeatures'\n\n\u002F\u002F This type defines the shape of our data.\n\u002F\u002F You can use a Zod schema here if you want.\nexport interface Payment {\n  id: string\n  amount: number\n  status: 'pending' | 'processing' | 'success' | 'failed'\n  email: string\n}\n\n\u002F\u002F Use `accessor` for data columns and `display` for columns without one.\nconst columnHelper = createColumnHelper\u003CDataTableFeatures, Payment>()\n\nexport const columns = columnHelper.columns([\n  columnHelper.accessor('status', {\n    header: 'Status',\n  }),\n  columnHelper.accessor('email', {\n    header: 'Email',\n  }),\n  columnHelper.accessor('amount', {\n    header: 'Amount',\n  }),\n])\n",[529,830,828],{"__ignoreMap":643},[534,832,834],{"className":833},[537],[501,835,836,838],{},[541,837,795],{}," Columns are where you define the core of what your table\nwill look like. They define the data that will be displayed, how it will be\nformatted, sorted and filtered.",[815,840,842,844],{"id":841},"datatable-component",[529,843,725],{}," component",[501,846,847,848,850],{},"Next, we'll create a ",[529,849,725],{}," component to render our table.",[501,852,853,856,857,860,861,754,864,867],{},[529,854,855],{},"useTable"," must be called during component setup so it can wire itself into Vue's reactivity. From there the table manages its own state: reads like ",[529,858,859],{},"table.getRowModel()"," in your template are reactive, so your markup updates automatically — no refs or change handlers required. We pass ",[529,862,863],{},"data",[529,865,866],{},"columns"," through getters so the table always reads the current prop values.",[637,869,874],{"className":870,"code":872,"language":873,"meta":643},[871],"language-vue","\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport type { ColumnDef } from '@tanstack\u002Fvue-table'\nimport { FlexRender, useTable } from '@tanstack\u002Fvue-table'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@\u002Fcomponents\u002Fui\u002Ftable'\nimport { features, type DataTableFeatures } from '.\u002Ffeatures'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CDataTableFeatures, TData>[]\n  data: TData[]\n}>()\n\nconst table = useTable({\n  features,\n  get data() { return props.data },\n  get columns() { return props.columns },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"border rounded-md\">\n    \u003CTable>\n      \u003CTableHeader>\n        \u003CTableRow v-for=\"headerGroup in table.getHeaderGroups()\" :key=\"headerGroup.id\">\n          \u003CTableHead v-for=\"header in headerGroup.headers\" :key=\"header.id\">\n            \u003CFlexRender v-if=\"!header.isPlaceholder\" :header=\"header\" \u002F>\n          \u003C\u002FTableHead>\n        \u003C\u002FTableRow>\n      \u003C\u002FTableHeader>\n      \u003CTableBody>\n        \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n          \u003CTableRow\n            v-for=\"row in table.getRowModel().rows\" :key=\"row.id\"\n            :data-state=\"row.getIsSelected() && 'selected'\"\n          >\n            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n              \u003CFlexRender :cell=\"cell\" \u002F>\n            \u003C\u002FTableCell>\n          \u003C\u002FTableRow>\n        \u003C\u002Ftemplate>\n        \u003Ctemplate v-else>\n          \u003CTableRow>\n            \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n              No results.\n            \u003C\u002FTableCell>\n          \u003C\u002FTableRow>\n        \u003C\u002Ftemplate>\n      \u003C\u002FTableBody>\n    \u003C\u002FTable>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[529,875,872],{"__ignoreMap":643},[501,877,878,881,882,885,886,889],{},[529,879,880],{},"\u003CFlexRender \u002F>"," takes the ",[529,883,884],{},"header"," or ",[529,887,888],{},"cell"," instance you pass it and renders whatever the column definition provides — a plain string, a render function, or a component.",[534,891,892,904],{},[501,893,894,897,898,900,901,770],{},[541,895,896],{},"Tip",": If you find yourself using ",[529,899,725],{}," in multiple places, this is the component you could make reusable by extracting it to ",[529,902,903],{},"components\u002Fui\u002Fdata-table.vue",[501,905,906],{},[529,907,908],{},"\u003CDataTable :columns=\"columns\" :data=\"data\" \u002F>",[534,910,912],{"className":911},[537],[501,913,914,917,918,921,922,924,925,930],{},[541,915,916],{},"Controlled state:"," because v9 owns table state internally, you won't write refs or ",[529,919,920],{},"on*Change"," handlers for most of this guide. If something outside the table needs to own a state slice (syncing filters to the URL, server-driven pagination, etc.), controlled state still exists — we'll manage the row selection slice externally in the ",[515,923,611],{"href":610}," section. See the ",[515,926,929],{"href":927,"rel":928},"https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fmigrating",[519],"migration guide"," for details.",[815,932,934],{"id":933},"render-the-table","Render the table",[501,936,937],{},"Finally, we'll render our table in our index component.",[637,939,942],{"className":940,"code":941,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\">\nimport type { Payment } from '.\u002Fcomponents\u002Fpayments\u002Fcolumns'\nimport { onMounted, ref } from 'vue'\nimport { columns } from '.\u002Fcomponents\u002Fpayments\u002Fcolumns'\nimport DataTable from '.\u002Fcomponents\u002Fpayments\u002Fdata-table.vue'\n\nconst data = ref\u003CPayment[]>([])\n\nasync function getData(): Promise\u003CPayment[]> {\n  \u002F\u002F Fetch data from your API here.\n  return [\n    {\n      id: '728ed52f',\n      amount: 100,\n      status: 'pending',\n      email: 'm@example.com',\n    },\n    \u002F\u002F ...\n  ]\n}\n\nonMounted(async () => {\n  data.value = await getData()\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"container py-10 mx-auto\">\n    \u003CDataTable :columns=\"columns\" :data=\"data\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[529,943,941],{"__ignoreMap":643},[505,945,947],{"id":946},"cell-formatting","Cell Formatting",[501,949,950],{},"Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.",[812,952,953,957,965,971,974,977,984,1038,1041,1044,1146,1149,1152,1171,1201,1204,1207,1216,1246,1249,1259,1274,1277,1284,1382,1385,1392,1444,1447,1450,1454,1457,1463,1469,1472,1475,1481,1487,1491,1494],{},[815,954,956],{"id":955},"update-columns-definition","Update columns definition",[501,958,959,960,754,962,964],{},"Update the ",[529,961,884],{},[529,963,888],{}," definitions for amount as follows:",[637,966,969],{"className":967,"code":968,"language":677,"meta":643},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { h } from 'vue'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.accessor('amount', {\n    header: () => h('div', { class: 'text-right' }, 'Amount'),\n    cell: ({ row }) => {\n      const amount = Number.parseFloat(row.getValue('amount'))\n\n      \u002F\u002F Format the amount as a dollar amount\n      const formatted = new Intl.NumberFormat('en-US', {\n        style: 'currency',\n        currency: 'USD',\n      }).format(amount)\n\n      return h('div', { class: 'text-right font-medium' }, formatted)\n    },\n  }),\n])\n",[529,970,968],{"__ignoreMap":643},[501,972,973],{},"You can use the same approach to format other cells and headers.\n",[505,975,582],{"id":976},"row-actions",[501,978,979,980,983],{},"Let's add row actions to our table. We'll use a ",[529,981,982],{},"\u003CDropdown \u002F>"," component for this.",[812,985,986,993,999,1002,1018,1024],{},[815,987,989,990,844],{"id":988},"add-the-following-into-your-datatabledropdownvue-component","Add the following into your ",[529,991,992],{},"DataTableDropDown.vue",[637,994,997],{"className":995,"code":996,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from '@lucide\u002Fvue'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton variant=\"ghost\" class=\"w-8 h-8 p-0\">\n        \u003Cspan class=\"sr-only\">Open menu\u003C\u002Fspan>\n        \u003CMoreHorizontal class=\"w-4 h-4\" \u002F>\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuSeparator \u002F>\n      \u003CDropdownMenuItem>View customer\u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C\u002FDropdownMenuItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n",[529,998,996],{"__ignoreMap":643},[815,1000,956],{"id":1001},"update-columns-definition-1",[501,1003,1004,1005,1008,1009,1011,1012,1014,1015,770],{},"Update our columns definition to add a new ",[529,1006,1007],{},"actions"," column. The ",[529,1010,1007],{}," cell returns a ",[529,1013,982],{}," component. Because the column doesn't read a data field, we define it with ",[529,1016,1017],{},"columnHelper.display",[637,1019,1022],{"className":1020,"code":1021,"language":677,"meta":643},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport DropdownAction from '@\u002Fcomponents\u002FDataTableDropDown.vue'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.display({\n    id: 'actions',\n    enableHiding: false,\n    cell: ({ row }) => {\n      const payment = row.original\n\n      return h('div', { class: 'relative' }, h(DropdownAction, {\n        payment,\n      }))\n    },\n  }),\n])\n",[529,1023,1021],{"__ignoreMap":643},[501,1025,1026,1027,1030,1031,1033,1034,1037],{},"You can access the row data using ",[529,1028,1029],{},"row.original"," in the ",[529,1032,888],{}," function. Use this to handle actions for your row eg. use the ",[529,1035,1036],{},"id"," to make a DELETE call to your API.",[505,1039,185],{"id":1040},"pagination",[501,1042,1043],{},"Next, we'll add pagination to our table.",[812,1045,1046,1050,1069,1073,1088,1094,1100,1104,1114,1120],{},[815,1047,1049],{"id":1048},"pagination-is-already-enabled","Pagination is already enabled",[501,1051,1052,1053,754,1056,1059,1060,1062,1063,1068],{},"Because our features object registers ",[529,1054,1055],{},"rowPaginationFeature",[529,1057,1058],{},"createPaginatedRowModel()",", the table automatically paginates rows into pages of 10 — there's nothing to add to ",[529,1061,855],{},". See the ",[515,1064,1067],{"href":1065,"rel":1066},"https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fpagination",[519],"pagination docs"," for more information on customizing page size and implementing manual pagination.",[815,1070,1072],{"id":1071},"add-pagination-controls","Add pagination controls",[501,1074,1075,1076,1079,1080,1083,1084,1087],{},"We can add pagination controls to our table using the ",[529,1077,1078],{},"\u003CButton \u002F>"," component and the ",[529,1081,1082],{},"table.previousPage()",", ",[529,1085,1086],{},"table.nextPage()"," API methods.",[637,1089,1092],{"className":1090,"code":1091,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>\n        { \u002F\u002F .... }\n      \u003C\u002FTable>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"flex items-center justify-end py-4 space-x-2\">\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanPreviousPage()\"\n        @click=\"table.previousPage()\"\n      >\n        Previous\n      \u003C\u002FButton>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanNextPage()\"\n        @click=\"table.nextPage()\"\n      >\n        Next\n      \u003C\u002FButton>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[529,1093,1091],{"__ignoreMap":643},[501,1095,1096,1097,1099],{},"See ",[515,1098,623],{"href":622}," section for a more advanced pagination component.",[815,1101,1103],{"id":1102},"change-the-page-size","Change the page size",[501,1105,1106,1107,1110,1111,770],{},"To change the page size, call ",[529,1108,1109],{},"table.setPageSize()",". To read the current pagination state — say, for a page indicator — read ",[529,1112,1113],{},"table.atoms.pagination.get()",[637,1115,1118],{"className":1116,"code":1117,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\">\nimport { computed } from 'vue'\n\n\u002F\u002F Reactive read — updates whenever the user changes pages.\nconst pagination = computed(() => table.atoms.pagination.get())\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton variant=\"outline\" size=\"sm\" @click=\"table.setPageSize(20)\">\n    Show 20 rows\n  \u003C\u002FButton>\n\n  \u003Cdiv class=\"text-sm text-muted-foreground\">\n    Page {{ pagination.pageIndex + 1 }} of {{ table.getPageCount() }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[529,1119,1117],{"__ignoreMap":643},[534,1121,1123],{"className":1122},[537],[501,1124,1125,1127,1128,1130,1131,1134,1135,1138,1139,1142,1143,770],{},[541,1126,795],{}," Atom reads like ",[529,1129,1113],{}," are reactive inside Vue tracking scopes — a template expression or a ",[529,1132,1133],{},"computed",". A bare ",[529,1136,1137],{},".get()"," at the top level of ",[529,1140,1141],{},"\u003Cscript setup>"," is a one-time snapshot, so wrap script-side reads in ",[529,1144,1145],{},"computed()",[505,1147,593],{"id":1148},"sorting",[501,1150,1151],{},"Let's make the email column sortable.",[501,1153,1154,1155,1158,1159,754,1161,1163,1164,1167,1168,1170],{},"The ",[529,1156,1157],{},"rowSortingFeature"," and sorted row model are already registered in our features object — along with the ",[529,1160,765],{},[529,1162,769],{}," sort functions that string columns resolve through the default ",[529,1165,1166],{},"auto"," setting — so there's nothing to change in ",[529,1169,725],{},". We just add the UI.",[812,1172,1173,1177,1184,1198],{},[815,1174,1176],{"id":1175},"make-header-cell-sortable","Make header cell sortable",[501,1178,1179,1180,1183],{},"We can update the ",[529,1181,1182],{},"email"," header cell to add sorting controls.",[637,1185,1196],{"className":1186,"code":1187,"highlights":1188,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { ArrowUpDown } from '@lucide\u002Fvue'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.accessor('email', {\n    header: ({ column }) => {\n      return h(Button, {\n        variant: 'ghost',\n        onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),\n      }, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])\n    },\n    cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),\n  }),\n])\n",[648,1189,1190,1191,1192,1193,1194,1195],3,8,9,10,11,12,13,[529,1197,1187],{"__ignoreMap":643},[501,1199,1200],{},"This will automatically sort the table (asc and desc) when the user toggles on the header cell. The table owns the sorting state — no wiring required.",[505,1202,599],{"id":1203},"filtering",[501,1205,1206],{},"Let's add a search input to filter emails in our table.",[501,1208,1154,1209,1212,1213,1215],{},[529,1210,1211],{},"columnFilteringFeature",", filtered row model, and the ",[529,1214,761],{}," filter function are already registered in our features object, so the only work left is rendering an input.",[812,1217,1218,1222,1228],{},[815,1219,1221],{"id":1220},"add-the-search-input","Add the search input",[637,1223,1226],{"className":1224,"code":1225,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { Input } from '@\u002Fcomponents\u002Fui\u002Finput'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"flex items-center py-4\">\n      \u003CInput\n        class=\"max-w-sm\"\n        placeholder=\"Filter emails...\"\n        :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n        @update:model-value=\"table.getColumn('email')?.setFilterValue($event)\"\n      \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C\u002FTable>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[529,1227,1225],{"__ignoreMap":643},[501,1229,1230,1231,1233,1234,1236,1237,1239,1240,1245],{},"Filtering is now enabled for the ",[529,1232,1182],{}," column. You can add filters to other columns as well — just remember that string-based filter references only resolve functions you've registered, so if another column needs a different built-in filter, add it to ",[529,1235,753],{}," in ",[529,1238,711],{}," first. See the ",[515,1241,1244],{"href":1242,"rel":1243},"https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fcolumn-filtering",[519],"filtering docs"," for more information on customizing filters.",[505,1247,605],{"id":1248},"visibility",[501,1250,1251,1252,1254,1255,1258],{},"Adding column visibility is fairly simple using ",[529,1253,653],{}," visibility API. The ",[529,1256,1257],{},"columnVisibilityFeature"," is already registered in our features object, so we only need to add the dropdown.",[812,1260,1261,1265,1271],{},[815,1262,1264],{"id":1263},"add-the-column-toggle-dropdown","Add the column toggle dropdown",[637,1266,1269],{"className":1267,"code":1268,"language":873,"meta":643},[871],"\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { ChevronDown } from '@lucide\u002Fvue'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"flex items-center py-4\">\n      \u003CInput\n        class=\"max-w-sm\"\n        placeholder=\"Filter emails...\"\n        :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n        @update:model-value=\"table.getColumn('email')?.setFilterValue($event)\"\n      \u002F>\n      \u003CDropdownMenu>\n        \u003CDropdownMenuTrigger as-child>\n          \u003CButton variant=\"outline\" class=\"ml-auto\">\n            Columns \u003CChevronDown class=\"w-4 h-4 ml-2\" \u002F>\n          \u003C\u002FButton>\n        \u003C\u002FDropdownMenuTrigger>\n        \u003CDropdownMenuContent align=\"end\">\n          \u003CDropdownMenuCheckboxItem\n            v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\"\n            :key=\"column.id\"\n            class=\"capitalize\"\n            :model-value=\"column.getIsVisible()\"\n            @update:model-value=\"(value) => {\n              column.toggleVisibility(!!value)\n            }\"\n          >\n            {{ column.id }}\n          \u003C\u002FDropdownMenuCheckboxItem>\n        \u003C\u002FDropdownMenuContent>\n      \u003C\u002FDropdownMenu>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C\u002FTable>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[529,1270,1268],{"__ignoreMap":643},[501,1272,1273],{},"This adds a dropdown menu that you can use to toggle column visibility.",[505,1275,611],{"id":1276},"row-selection",[501,1278,1279,1280,1283],{},"Next, we're going to add row selection to our table. The ",[529,1281,1282],{},"rowSelectionFeature"," is already registered in our features object, so the table tracks selection for us — we just render the checkboxes.",[812,1285,1286,1290,1306,1309,1336,1340,1351,1358,1364,1368,1375],{},[815,1287,1289],{"id":1288},"update-column-definitions","Update column definitions",[637,1291,1304],{"className":1292,"code":1293,"highlights":1294,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { Checkbox } from '@\u002Fcomponents\u002Fui\u002Fcheckbox'\n\nexport const columns = columnHelper.columns([\n  columnHelper.display({\n    id: 'select',\n    header: ({ table }) => h(Checkbox, {\n      'modelValue': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),\n      'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),\n      'ariaLabel': 'Select all',\n    }),\n    cell: ({ row }) => h(Checkbox, {\n      'modelValue': row.getIsSelected(),\n      'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),\n      'ariaLabel': 'Select row',\n    }),\n    enableSorting: false,\n    enableHiding: false,\n  }),\n  \u002F\u002F ...\n])\n",[648,1295,1296,1297,1190,1191,1192,1193,1194,1195,1298,1299,1300,1301,1302,1303],5,6,7,14,15,16,17,18,19,[529,1305,1293],{"__ignoreMap":643},[501,1307,1308],{},"This adds a checkbox to each row and a checkbox in the header to select all rows.",[534,1310,1312],{"className":1311},[537],[501,1313,1314,1316,1317,1320,1321,1324,1325,1328,1329,1331,1332,1335],{},[541,1315,795],{}," In v9, ",[529,1318,1319],{},"table.getIsSomePageRowsSelected()"," returns ",[529,1322,1323],{},"true"," whenever at least one page row is selected — even when all are. The header checkbox still works because ",[529,1326,1327],{},"table.getIsAllPageRowsSelected() || ..."," short-circuits to ",[529,1330,1323],{}," at full selection before the ",[529,1333,1334],{},"'indeterminate'"," branch is reached.",[815,1337,1339],{"id":1338},"manage-the-selection-state-externally","Manage the selection state externally",[501,1341,1342,1343,1346,1347,1350],{},"The table tracks selection internally by default. To show how a slice can live outside the table — handy when your page needs to read or drive the selection — we'll own just this one slice with a Vue ",[529,1344,1345],{},"ref"," and leave everything else internal. Expose the current value with a ",[529,1348,1349],{},"state"," getter, and resolve the updater in the matching callback:",[637,1352,1356],{"className":1353,"code":1354,"highlights":1355,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Fdata-table.vue\nimport type { RowSelectionState } from '@tanstack\u002Fvue-table'\nimport { ref } from 'vue'\n\n\u002F\u002F Keep row selection outside the table so the rest of the app can read or update it.\nconst rowSelection = ref\u003CRowSelectionState>({})\n\nconst table = useTable({\n  features,\n  get data() { return props.data },\n  get columns() { return props.columns },\n  state: {\n    get rowSelection() { return rowSelection.value },\n  },\n  onRowSelectionChange: (updater) => {\n    rowSelection.value = typeof updater === 'function' ? updater(rowSelection.value) : updater\n  },\n})\n",[648,1189,1295,1296,1297,1195,1298,1299,1300,1301,1302],[529,1357,1354],{"__ignoreMap":643},[501,1359,1360,1363],{},[529,1361,1362],{},"rowSelection.value"," can now be read or written anywhere in the component, and the table stays in sync.",[815,1365,1367],{"id":1366},"show-selected-rows","Show selected rows",[501,1369,1370,1371,1374],{},"You can show the number of selected rows using the ",[529,1372,1373],{},"table.getFilteredSelectedRowModel()"," API.",[637,1376,1380],{"className":1377,"code":1378,"highlights":1379,"language":873,"meta":678},[871],"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n        \u003CTable \u002F>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"flex items-center justify-end space-x-2 py-4\">\n      \u003Cdiv class=\"flex-1 text-sm text-muted-foreground\">\n        {{ table.getFilteredSelectedRowModel().rows.length }} of\n        {{ table.getFilteredRowModel().rows.length }} row(s) selected.\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"space-x-2\">\n        \u003CPaginationButtons \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[1190,1191,1192,1193],[529,1381,1378],{"__ignoreMap":643},[505,1383,617],{"id":1384},"expanding",[501,1386,1387,1388,1391],{},"Let's make rows expandable. The ",[529,1389,1390],{},"rowExpandingFeature"," and expanded row model are already registered in our features object, so the table tracks expanded state for us — we just add the UI.",[812,1393,1394,1401,1407,1414,1420,1430,1434,1437],{},[815,1395,1397,1398],{"id":1396},"update-datatable","Update ",[529,1399,1400],{},"\u003CDataTable>",[501,1402,959,1403,1406],{},[529,1404,1405],{},"\u003CTableBody>"," to render an extra row whenever a row is expanded:",[637,1408,1412],{"className":1409,"code":1410,"highlights":1411,"language":873,"meta":678},[871],"\u003CTableBody>\n  \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n    \u003Ctemplate v-for=\"row in table.getRowModel().rows\" :key=\"row.id\">\n      \u003CTableRow :data-state=\"row.getIsSelected() && 'selected'\">\n        \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n          \u003CFlexRender :cell=\"cell\" \u002F>\n        \u003C\u002FTableCell>\n      \u003C\u002FTableRow>\n      \u003CTableRow v-if=\"row.getIsExpanded()\">\n        \u003CTableCell :colspan=\"row.getAllCells().length\">\n          {{ JSON.stringify(row.original) }}\n        \u003C\u002FTableCell>\n      \u003C\u002FTableRow>\n    \u003C\u002Ftemplate>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate v-else>\n    \u003CTableRow>\n      \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n        No results.\n      \u003C\u002FTableCell>\n    \u003C\u002FTableRow>\n  \u003C\u002Ftemplate>\n\u003C\u002FTableBody>\n",[1189,1191,1192,1193,1194,1195],[529,1413,1410],{"__ignoreMap":643},[815,1415,1417,1418,844],{"id":1416},"add-the-expand-action-to-the-datatabledropdownvue-component","Add the expand action to the ",[529,1419,992],{},[637,1421,1428],{"className":1422,"code":1423,"highlights":1424,"language":873,"meta":678},[871],"\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from '@lucide\u002Fvue'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\ndefineEmits\u003C{\n  (e: 'expand'): void\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton variant=\"ghost\" class=\"w-8 h-8 p-0\">\n        \u003Cspan class=\"sr-only\">Open menu\u003C\u002Fspan>\n        \u003CMoreHorizontal class=\"w-4 h-4\" \u002F>\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem @click=\"$emit('expand')\">\n        Expand\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuSeparator \u002F>\n      \u003CDropdownMenuItem>View customer\u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C\u002FDropdownMenuItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n",[1194,1195,1298,1425,1426,1427],34,35,36,[529,1429,1423],{"__ignoreMap":643},[815,1431,1433],{"id":1432},"make-rows-expandable","Make rows expandable",[501,1435,1436],{},"Now we can update the action cell to add the expand control.",[637,1438,1442],{"className":1439,"code":1440,"highlights":1441,"language":677,"meta":678},[675],"\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.display({\n    id: 'actions',\n    enableHiding: false,\n    cell: ({ row }) => {\n      const payment = row.original\n\n      return h('div', { class: 'relative' }, h(DropdownAction, {\n        payment,\n        onExpand: () => row.toggleExpanded(),\n      }))\n    },\n  }),\n])\n",[1194],[529,1443,1440],{"__ignoreMap":643},[505,1445,623],{"id":1446},"reusable-components",[501,1448,1449],{},"Here are some components you can use to build your data tables. These examples are adapted from the Tasks demo.",[815,1451,1453],{"id":1452},"column-header","Column header",[501,1455,1456],{},"Make any column header sortable and hideable.",[637,1458,1461],{"className":1459,"code":1460,"language":873,"meta":678},[871],"\u003Cscript setup lang=\"ts\">\nimport type { Column } from '@tanstack\u002Fvue-table'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport ArrowDownIcon from '~icons\u002Fradix-icons\u002Farrow-down'\nimport ArrowUpIcon from '~icons\u002Fradix-icons\u002Farrow-up'\nimport CaretSortIcon from '~icons\u002Fradix-icons\u002Fcaret-sort'\nimport EyeNoneIcon from '~icons\u002Fradix-icons\u002Feye-none'\n\nimport { cn } from '@\u002Flib\u002Futils'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ninterface DataTableColumnHeaderProps {\n  column: Column\u003CDataTableFeatures, Task>\n  title: string\n}\n\ndefineProps\u003CDataTableColumnHeaderProps>()\n\u003C\u002Fscript>\n\n\u003Cscript lang=\"ts\">\nexport default {\n  inheritAttrs: false,\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"column.getCanSort()\" :class=\"cn('flex items-center space-x-2', $attrs.class ?? '')\">\n    \u003CDropdownMenu>\n      \u003CDropdownMenuTrigger as-child>\n        \u003CButton\n          variant=\"ghost\"\n          size=\"sm\"\n          class=\"-ml-3 h-8 data-[state=open]:bg-accent\"\n        >\n          \u003Cspan>{{ title }}\u003C\u002Fspan>\n          \u003CArrowDownIcon v-if=\"column.getIsSorted() === 'desc'\" class=\"w-4 h-4 ml-2\" \u002F>\n          \u003CArrowUpIcon v-else-if=\" column.getIsSorted() === 'asc'\" class=\"w-4 h-4 ml-2\" \u002F>\n          \u003CCaretSortIcon v-else class=\"w-4 h-4 ml-2\" \u002F>\n        \u003C\u002FButton>\n      \u003C\u002FDropdownMenuTrigger>\n      \u003CDropdownMenuContent align=\"start\">\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(false)\">\n          \u003CArrowUpIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Asc\n        \u003C\u002FDropdownMenuItem>\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(true)\">\n          \u003CArrowDownIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Desc\n        \u003C\u002FDropdownMenuItem>\n        \u003CDropdownMenuSeparator \u002F>\n        \u003CDropdownMenuItem @click=\"column.toggleVisibility(false)\">\n          \u003CEyeNoneIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Hide\n        \u003C\u002FDropdownMenuItem>\n      \u003C\u002FDropdownMenuContent>\n    \u003C\u002FDropdownMenu>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv v-else :class=\"$attrs.class\">\n    {{ title }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n",[529,1462,1460],{"__ignoreMap":643},[637,1464,1467],{"className":1465,"code":1466,"language":677,"meta":678},[675],"export const columns = columnHelper.columns([\n  columnHelper.accessor('email', {\n    header: ({ column }) => (\n      h(DataTableColumnHeader, {\n        column,\n        title: 'Email',\n      })\n    ),\n  }),\n])\n",[529,1468,1466],{"__ignoreMap":643},[815,1470,185],{"id":1471},"pagination-1",[501,1473,1474],{},"Add pagination controls to your table including page size and selection count.",[637,1476,1479],{"className":1477,"code":1478,"language":873,"meta":678},[871],"\u003Cscript setup lang=\"ts\">\nimport { type Table } from '@tanstack\u002Fvue-table'\nimport { computed } from 'vue'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport ChevronLeftIcon from '~icons\u002Fradix-icons\u002Fchevron-left'\nimport ChevronRightIcon from '~icons\u002Fradix-icons\u002Fchevron-right'\nimport DoubleArrowLeftIcon from '~icons\u002Fradix-icons\u002Fdouble-arrow-left'\nimport DoubleArrowRightIcon from '~icons\u002Fradix-icons\u002Fdouble-arrow-right'\n\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@\u002Fcomponents\u002Fui\u002Fselect'\n\ninterface DataTablePaginationProps {\n  table: Table\u003CDataTableFeatures, Task>\n}\nconst props = defineProps\u003CDataTablePaginationProps>()\n\n\u002F\u002F Reactive atom read — a bare `.get()` in script setup is a one-time snapshot.\nconst pagination = computed(() => props.table.atoms.pagination.get())\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"flex items-center justify-between px-2\">\n    \u003Cdiv class=\"flex-1 text-sm text-muted-foreground\">\n      {{ table.getFilteredSelectedRowModel().rows.length }} of\n      {{ table.getFilteredRowModel().rows.length }} row(s) selected.\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"flex items-center space-x-6 lg:space-x-8\">\n      \u003Cdiv class=\"flex items-center space-x-2\">\n        \u003Cp class=\"text-sm font-medium\">\n          Rows per page\n        \u003C\u002Fp>\n        \u003CSelect\n          :model-value=\"`${pagination.pageSize}`\"\n          @update:model-value=\"table.setPageSize\"\n        >\n          \u003CSelectTrigger class=\"h-8 w-[70px]\">\n            \u003CSelectValue :placeholder=\"`${pagination.pageSize}`\" \u002F>\n          \u003C\u002FSelectTrigger>\n          \u003CSelectContent side=\"top\">\n            \u003CSelectItem v-for=\"pageSize in [10, 20, 30, 40, 50]\" :key=\"pageSize\" :value=\"`${pageSize}`\">\n              {{ pageSize }}\n            \u003C\u002FSelectItem>\n          \u003C\u002FSelectContent>\n        \u003C\u002FSelect>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"flex w-[100px] items-center justify-center text-sm font-medium\">\n        Page {{ pagination.pageIndex + 1 }} of\n        {{ table.getPageCount() }}\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"flex items-center space-x-2\">\n        \u003CButton\n          variant=\"outline\"\n          class=\"hidden w-8 h-8 p-0 lg:flex\"\n          :disabled=\"!table.getCanPreviousPage()\"\n          @click=\"table.setPageIndex(0)\"\n        >\n          \u003Cspan class=\"sr-only\">Go to first page\u003C\u002Fspan>\n          \u003CDoubleArrowLeftIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"w-8 h-8 p-0\"\n          :disabled=\"!table.getCanPreviousPage()\"\n          @click=\"table.previousPage()\"\n        >\n          \u003Cspan class=\"sr-only\">Go to previous page\u003C\u002Fspan>\n          \u003CChevronLeftIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"w-8 h-8 p-0\"\n          :disabled=\"!table.getCanNextPage()\"\n          @click=\"table.nextPage()\"\n        >\n          \u003Cspan class=\"sr-only\">Go to next page\u003C\u002Fspan>\n          \u003CChevronRightIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"hidden w-8 h-8 p-0 lg:flex\"\n          :disabled=\"!table.getCanNextPage()\"\n          @click=\"table.setPageIndex(table.getPageCount() - 1)\"\n        >\n          \u003Cspan class=\"sr-only\">Go to last page\u003C\u002Fspan>\n          \u003CDoubleArrowRightIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n",[529,1480,1478],{"__ignoreMap":643},[637,1482,1485],{"className":1483,"code":1484,"language":873,"meta":643},[871],"\u003CDataTablePagination :table=\"table\" \u002F>\n",[529,1486,1484],{"__ignoreMap":643},[815,1488,1490],{"id":1489},"column-toggle","Column toggle",[501,1492,1493],{},"A component to toggle column visibility.",[637,1495,1498],{"className":1496,"code":1497,"language":873,"meta":678},[871],"\u003Cscript setup lang=\"ts\">\nimport type { Table } from '@tanstack\u002Fvue-table'\nimport { computed } from 'vue'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport MixerHorizontalIcon from '~icons\u002Fradix-icons\u002Fmixer-horizontal'\n\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ninterface DataTableViewOptionsProps {\n  table: Table\u003CDataTableFeatures, Task>\n}\n\nconst props = defineProps\u003CDataTableViewOptionsProps>()\n\nconst columns = computed(() => props.table.getAllColumns()\n  .filter(\n    column =>\n      typeof column.accessorFn !== 'undefined' && column.getCanHide(),\n  ))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        class=\"hidden h-8 ml-auto lg:flex\"\n      >\n        \u003CMixerHorizontalIcon class=\"w-4 h-4 mr-2\" \u002F>\n        View\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\" class=\"w-[150px]\">\n      \u003CDropdownMenuLabel>Toggle columns\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuSeparator \u002F>\n\n      \u003CDropdownMenuCheckboxItem\n        v-for=\"column in columns\"\n        :key=\"column.id\"\n        class=\"capitalize\"\n        :modelValue=\"column.getIsVisible()\"\n        @update:modelValue=\"(value) => column.toggleVisibility(!!value)\"\n      >\n        {{ column.id }}\n      \u003C\u002FDropdownMenuCheckboxItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n",[529,1499,1497],{"__ignoreMap":643},{"title":643,"searchDepth":648,"depth":648,"links":1501},[1502,1503,1504,1505,1506,1507,1508,1514,1517,1518,1519,1520,1521,1522,1523,1524],{"id":507,"depth":648,"text":409},{"id":547,"depth":648,"text":548},{"id":626,"depth":648,"text":299},{"id":667,"depth":648,"text":668},{"id":683,"depth":648,"text":684},{"id":743,"depth":648,"text":570},{"id":807,"depth":648,"text":576,"children":1509},[1510,1511,1513],{"id":817,"depth":1189,"text":818},{"id":841,"depth":1189,"text":1512},"\u003CDataTable \u002F> component",{"id":933,"depth":1189,"text":934},{"id":946,"depth":648,"text":947,"children":1515},[1516],{"id":955,"depth":1189,"text":956},{"id":976,"depth":648,"text":582},{"id":1040,"depth":648,"text":185},{"id":1148,"depth":648,"text":593},{"id":1203,"depth":648,"text":599},{"id":1248,"depth":648,"text":605},{"id":1276,"depth":648,"text":611},{"id":1384,"depth":648,"text":617},{"id":1446,"depth":648,"text":623,"children":1525},[1526,1527,1528],{"id":1452,"depth":1189,"text":1453},{"id":1471,"depth":1189,"text":185},{"id":1489,"depth":1189,"text":1490},"Powerful table and datagrids built using TanStack Table.","md",null,{"component":204},"---\ntitle: Data Table\ndescription: Powerful table and datagrids built using TanStack Table.\ncomponent: true\n---\n\n::component-preview\n---\nname: DataTableDemo\ndescription: A data table with sorting, filtering, and pagination.\nalign: start\npreviewClass: items-start h-auto px-4 md:px-8\n---\n::\n\n::vue-school-link{class=\"mt-6\" lesson=\"data-tables-and-sonner-in-shadcn-vue\" placement=\"top\"}\nWatch a Vue School video about data tables in shadcn-vue.\n::\n\n## Introduction\n\nEvery data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.\n\nIt doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fintroduction#what-is-headless-ui) provides.\n\nSo instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.\n\nWe'll start with the basic `\u003CTable \u002F>` component and build a complex data table from scratch.\n\n::callout{class=\"mt-4\"}\n\n**Tip:** If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.\n\n::\n\n## Table of Contents\n\nThis guide will show you how to use [TanStack Table](https:\u002F\u002Ftanstack.com\u002Ftable) and the `\u003CTable \u002F>` component to build your own custom data table. We'll cover the following topics:\n\n- [Set up Table Features](#set-up-table-features)\n- [Basic Table](#basic-table)\n- [Row Actions](#row-actions)\n- [Pagination](#pagination)\n- [Sorting](#sorting)\n- [Filtering](#filtering)\n- [Visibility](#visibility)\n- [Row Selection](#row-selection)\n- [Expanding](#expanding)\n- [Reusable Components](#reusable-components)\n\n\n## Installation\n\n1. Add the `\u003CTable \u002F>` component to your project:\n\n```bash\nnpx shadcn-vue@latest add table\n```\n\n2. Add the `@tanstack\u002Fvue-table` dependency. This guide uses **TanStack Table v9**:\n\n```bash\nnpm install @tanstack\u002Fvue-table\n```\n\n\n\n## Prerequisites\n\nWe are going to build a table to show recent payments. Here's what our data looks like:\n\n```ts showLineNumbers\ninterface Payment {\n  id: string\n  amount: number\n  status: 'pending' | 'processing' | 'success' | 'failed'\n  email: string\n}\n\nexport const payments: Payment[] = [\n  {\n    id: '728ed52f',\n    amount: 100,\n    status: 'pending',\n    email: 'm@example.com',\n  },\n  {\n    id: '489e1d42',\n    amount: 125,\n    status: 'processing',\n    email: 'example@gmail.com',\n  },\n  \u002F\u002F ...\n]\n```\n\n## Project Structure\n\nStart by creating the following file structure:\n\n```ansi\n components\n    └── payments\n          ├── columns.ts\n          ├── features.ts\n          ├── data-table.vue\n          ├── data-table-dropdown.vue\n└── app.vue\n```\n\nI'm using a Nuxt example here but this works for any other Vue framework.\n\n- `columns.ts` It will contain our column definitions.\n- `features.ts` It will contain the shared `features` object that tells TanStack Table which behavior to enable.\n- `data-table.vue` It will contain our `\u003CDataTable \u002F>` component.\n- `data-table-dropdown.vue` It will contain our `\u003CDropdownAction \u002F>` component.\n- `app.vue` This is where we'll fetch data and render our table.\n\n## Set up Table Features\n\nTanStack Table v9 is feature-based: you opt into the behavior you want — sorting, filtering, pagination, and so on — by declaring it with `tableFeatures()`. Anything you don't register is tree-shaken out of your bundle. That includes the built-in filter and sort functions: register the ones your columns rely on under `filterFns` and `sortFns`. Our email filter uses `includesString`, and string columns sort with `alphanumeric` \u002F `text`.\n\nWe'll define the `features` object once in `features.ts` and share it between our column definitions and the `\u003CDataTable \u002F>` component.\n\n```ts showLineNumbers\n\u002F\u002F components\u002Fpayments\u002Ffeatures.ts\nimport {\n  columnFilteringFeature,\n  columnVisibilityFeature,\n  createExpandedRowModel,\n  createFilteredRowModel,\n  createPaginatedRowModel,\n  createSortedRowModel,\n  filterFn_includesString,\n  rowExpandingFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  sortFn_alphanumeric,\n  sortFn_text,\n  tableFeatures,\n} from '@tanstack\u002Fvue-table'\n\n\u002F\u002F New in v9: declare the features this table uses — anything you don't\n\u002F\u002F register is tree-shaken out of the bundle.\nexport const features = tableFeatures({\n  columnFilteringFeature,\n  columnVisibilityFeature,\n  rowExpandingFeature,\n  rowPaginationFeature,\n  rowSelectionFeature,\n  rowSortingFeature,\n  expandedRowModel: createExpandedRowModel(),\n  filteredRowModel: createFilteredRowModel(),\n  paginatedRowModel: createPaginatedRowModel(),\n  sortedRowModel: createSortedRowModel(),\n  filterFns: { includesString: filterFn_includesString },\n  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },\n})\n\n\u002F\u002F Pass this as the first generic argument to `ColumnDef`, `Column`, `Table`,\n\u002F\u002F and `Row` so each type knows which feature APIs are available.\nexport type DataTableFeatures = typeof features\n```\n\n::callout{class=\"mt-4\"}\n\n**Note:** The core row model is always included, so you never register it yourself. Row models for optional features are created with `create*RowModel()` and registered on the features object — there are no more `get*RowModel` table options.\n\n::\n\n## Basic Table\n\nLet's start by building a basic table.\n\n\u003CSteps>\n\n### Column Definitions\n\nFirst, we'll define our columns in the `columns.ts` file using a column helper typed with our features.\n\n```ts showLineNumbers\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { createColumnHelper } from '@tanstack\u002Fvue-table'\nimport type { DataTableFeatures } from '.\u002Ffeatures'\n\n\u002F\u002F This type defines the shape of our data.\n\u002F\u002F You can use a Zod schema here if you want.\nexport interface Payment {\n  id: string\n  amount: number\n  status: 'pending' | 'processing' | 'success' | 'failed'\n  email: string\n}\n\n\u002F\u002F Use `accessor` for data columns and `display` for columns without one.\nconst columnHelper = createColumnHelper\u003CDataTableFeatures, Payment>()\n\nexport const columns = columnHelper.columns([\n  columnHelper.accessor('status', {\n    header: 'Status',\n  }),\n  columnHelper.accessor('email', {\n    header: 'Email',\n  }),\n  columnHelper.accessor('amount', {\n    header: 'Amount',\n  }),\n])\n```\n\n::callout{class=\"mt-4\"}\n\n**Note:** Columns are where you define the core of what your table\nwill look like. They define the data that will be displayed, how it will be\nformatted, sorted and filtered.\n\n::\n\n### `\u003CDataTable \u002F>` component\n\nNext, we'll create a `\u003CDataTable \u002F>` component to render our table.\n\n`useTable` must be called during component setup so it can wire itself into Vue's reactivity. From there the table manages its own state: reads like `table.getRowModel()` in your template are reactive, so your markup updates automatically — no refs or change handlers required. We pass `data` and `columns` through getters so the table always reads the current prop values.\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport type { ColumnDef } from '@tanstack\u002Fvue-table'\nimport { FlexRender, useTable } from '@tanstack\u002Fvue-table'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@\u002Fcomponents\u002Fui\u002Ftable'\nimport { features, type DataTableFeatures } from '.\u002Ffeatures'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CDataTableFeatures, TData>[]\n  data: TData[]\n}>()\n\nconst table = useTable({\n  features,\n  get data() { return props.data },\n  get columns() { return props.columns },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"border rounded-md\">\n    \u003CTable>\n      \u003CTableHeader>\n        \u003CTableRow v-for=\"headerGroup in table.getHeaderGroups()\" :key=\"headerGroup.id\">\n          \u003CTableHead v-for=\"header in headerGroup.headers\" :key=\"header.id\">\n            \u003CFlexRender v-if=\"!header.isPlaceholder\" :header=\"header\" \u002F>\n          \u003C\u002FTableHead>\n        \u003C\u002FTableRow>\n      \u003C\u002FTableHeader>\n      \u003CTableBody>\n        \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n          \u003CTableRow\n            v-for=\"row in table.getRowModel().rows\" :key=\"row.id\"\n            :data-state=\"row.getIsSelected() && 'selected'\"\n          >\n            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n              \u003CFlexRender :cell=\"cell\" \u002F>\n            \u003C\u002FTableCell>\n          \u003C\u002FTableRow>\n        \u003C\u002Ftemplate>\n        \u003Ctemplate v-else>\n          \u003CTableRow>\n            \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n              No results.\n            \u003C\u002FTableCell>\n          \u003C\u002FTableRow>\n        \u003C\u002Ftemplate>\n      \u003C\u002FTableBody>\n    \u003C\u002FTable>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n`\u003CFlexRender \u002F>` takes the `header` or `cell` instance you pass it and renders whatever the column definition provides — a plain string, a render function, or a component.\n\n::callout\n\n**Tip**: If you find yourself using `\u003CDataTable \u002F>` in multiple places, this is the component you could make reusable by extracting it to `components\u002Fui\u002Fdata-table.vue`.\n\n`\u003CDataTable :columns=\"columns\" :data=\"data\" \u002F>`\n\n::\n\n::callout{class=\"mt-4\"}\n\n**Controlled state:** because v9 owns table state internally, you won't write refs or `on*Change` handlers for most of this guide. If something outside the table needs to own a state slice (syncing filters to the URL, server-driven pagination, etc.), controlled state still exists — we'll manage the row selection slice externally in the [Row Selection](#row-selection) section. See the [migration guide](https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fmigrating) for details.\n\n::\n\n### Render the table\n\nFinally, we'll render our table in our index component.\n\n```vue\n\u003Cscript setup lang=\"ts\">\nimport type { Payment } from '.\u002Fcomponents\u002Fpayments\u002Fcolumns'\nimport { onMounted, ref } from 'vue'\nimport { columns } from '.\u002Fcomponents\u002Fpayments\u002Fcolumns'\nimport DataTable from '.\u002Fcomponents\u002Fpayments\u002Fdata-table.vue'\n\nconst data = ref\u003CPayment[]>([])\n\nasync function getData(): Promise\u003CPayment[]> {\n  \u002F\u002F Fetch data from your API here.\n  return [\n    {\n      id: '728ed52f',\n      amount: 100,\n      status: 'pending',\n      email: 'm@example.com',\n    },\n    \u002F\u002F ...\n  ]\n}\n\nonMounted(async () => {\n  data.value = await getData()\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"container py-10 mx-auto\">\n    \u003CDataTable :columns=\"columns\" :data=\"data\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n\u003C\u002FSteps>\n\n## Cell Formatting\n\nLet's format the amount cell to display the dollar amount. We'll also align the cell to the right.\n\n\u003CSteps>\n\n### Update columns definition\n\nUpdate the `header` and `cell` definitions for amount as follows:\n\n```ts\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { h } from 'vue'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.accessor('amount', {\n    header: () => h('div', { class: 'text-right' }, 'Amount'),\n    cell: ({ row }) => {\n      const amount = Number.parseFloat(row.getValue('amount'))\n\n      \u002F\u002F Format the amount as a dollar amount\n      const formatted = new Intl.NumberFormat('en-US', {\n        style: 'currency',\n        currency: 'USD',\n      }).format(amount)\n\n      return h('div', { class: 'text-right font-medium' }, formatted)\n    },\n  }),\n])\n```\nYou can use the same approach to format other cells and headers.\n\u003C\u002FSteps>\n\n## Row Actions\n\nLet's add row actions to our table. We'll use a `\u003CDropdown \u002F>` component for this.\n\n\u003CSteps>\n\n### Add the following into your `DataTableDropDown.vue` component\n\n```vue\n\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from '@lucide\u002Fvue'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton variant=\"ghost\" class=\"w-8 h-8 p-0\">\n        \u003Cspan class=\"sr-only\">Open menu\u003C\u002Fspan>\n        \u003CMoreHorizontal class=\"w-4 h-4\" \u002F>\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuSeparator \u002F>\n      \u003CDropdownMenuItem>View customer\u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C\u002FDropdownMenuItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n```\n\n### Update columns definition\n\nUpdate our columns definition to add a new `actions` column. The `actions` cell returns a `\u003CDropdown \u002F>` component. Because the column doesn't read a data field, we define it with `columnHelper.display`.\n\n```ts\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport DropdownAction from '@\u002Fcomponents\u002FDataTableDropDown.vue'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.display({\n    id: 'actions',\n    enableHiding: false,\n    cell: ({ row }) => {\n      const payment = row.original\n\n      return h('div', { class: 'relative' }, h(DropdownAction, {\n        payment,\n      }))\n    },\n  }),\n])\n```\n\nYou can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.\n\n\u003C\u002FSteps>\n\n## Pagination\n\nNext, we'll add pagination to our table.\n\n\u003CSteps>\n\n### Pagination is already enabled\n\nBecause our features object registers `rowPaginationFeature` and `createPaginatedRowModel()`, the table automatically paginates rows into pages of 10 — there's nothing to add to `useTable`. See the [pagination docs](https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fpagination) for more information on customizing page size and implementing manual pagination.\n\n### Add pagination controls\n\nWe can add pagination controls to our table using the `\u003CButton \u002F>` component and the `table.previousPage()`, `table.nextPage()` API methods.\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>\n        { \u002F\u002F .... }\n      \u003C\u002FTable>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"flex items-center justify-end py-4 space-x-2\">\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanPreviousPage()\"\n        @click=\"table.previousPage()\"\n      >\n        Previous\n      \u003C\u002FButton>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanNextPage()\"\n        @click=\"table.nextPage()\"\n      >\n        Next\n      \u003C\u002FButton>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nSee [Reusable Components](#reusable-components) section for a more advanced pagination component.\n\n### Change the page size\n\nTo change the page size, call `table.setPageSize()`. To read the current pagination state — say, for a page indicator — read `table.atoms.pagination.get()`.\n\n```vue\n\u003Cscript setup lang=\"ts\">\nimport { computed } from 'vue'\n\n\u002F\u002F Reactive read — updates whenever the user changes pages.\nconst pagination = computed(() => table.atoms.pagination.get())\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton variant=\"outline\" size=\"sm\" @click=\"table.setPageSize(20)\">\n    Show 20 rows\n  \u003C\u002FButton>\n\n  \u003Cdiv class=\"text-sm text-muted-foreground\">\n    Page {{ pagination.pageIndex + 1 }} of {{ table.getPageCount() }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n::callout{class=\"mt-4\"}\n\n**Note:** Atom reads like `table.atoms.pagination.get()` are reactive inside Vue tracking scopes — a template expression or a `computed`. A bare `.get()` at the top level of `\u003Cscript setup>` is a one-time snapshot, so wrap script-side reads in `computed()`.\n\n::\n\n\u003C\u002FSteps>\n\n## Sorting\n\nLet's make the email column sortable.\n\nThe `rowSortingFeature` and sorted row model are already registered in our features object — along with the `alphanumeric` and `text` sort functions that string columns resolve through the default `auto` setting — so there's nothing to change in `\u003CDataTable \u002F>`. We just add the UI.\n\n\u003CSteps>\n\n### Make header cell sortable\n\nWe can update the `email` header cell to add sorting controls.\n\n```ts showLineNumbers {2-3,8-13}\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { ArrowUpDown } from '@lucide\u002Fvue'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.accessor('email', {\n    header: ({ column }) => {\n      return h(Button, {\n        variant: 'ghost',\n        onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),\n      }, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])\n    },\n    cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),\n  }),\n])\n```\n\nThis will automatically sort the table (asc and desc) when the user toggles on the header cell. The table owns the sorting state — no wiring required.\n\n\u003C\u002FSteps>\n\n## Filtering\n\nLet's add a search input to filter emails in our table.\n\nThe `columnFilteringFeature`, filtered row model, and the `includesString` filter function are already registered in our features object, so the only work left is rendering an input.\n\n\u003CSteps>\n\n### Add the search input\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { Input } from '@\u002Fcomponents\u002Fui\u002Finput'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"flex items-center py-4\">\n      \u003CInput\n        class=\"max-w-sm\"\n        placeholder=\"Filter emails...\"\n        :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n        @update:model-value=\"table.getColumn('email')?.setFilterValue($event)\"\n      \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C\u002FTable>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nFiltering is now enabled for the `email` column. You can add filters to other columns as well — just remember that string-based filter references only resolve functions you've registered, so if another column needs a different built-in filter, add it to `filterFns` in `features.ts` first. See the [filtering docs](https:\u002F\u002Ftanstack.com\u002Ftable\u002Flatest\u002Fdocs\u002Fframework\u002Fvue\u002Fguide\u002Fcolumn-filtering) for more information on customizing filters.\n\n\u003C\u002FSteps>\n\n## Visibility\n\nAdding column visibility is fairly simple using `@tanstack\u002Fvue-table` visibility API. The `columnVisibilityFeature` is already registered in our features object, so we only need to add the dropdown.\n\n\u003CSteps>\n\n### Add the column toggle dropdown\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"TData\">\nimport { ChevronDown } from '@lucide\u002Fvue'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\n\u002F\u002F ...the rest of the script stays the same\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"flex items-center py-4\">\n      \u003CInput\n        class=\"max-w-sm\"\n        placeholder=\"Filter emails...\"\n        :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n        @update:model-value=\"table.getColumn('email')?.setFilterValue($event)\"\n      \u002F>\n      \u003CDropdownMenu>\n        \u003CDropdownMenuTrigger as-child>\n          \u003CButton variant=\"outline\" class=\"ml-auto\">\n            Columns \u003CChevronDown class=\"w-4 h-4 ml-2\" \u002F>\n          \u003C\u002FButton>\n        \u003C\u002FDropdownMenuTrigger>\n        \u003CDropdownMenuContent align=\"end\">\n          \u003CDropdownMenuCheckboxItem\n            v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\"\n            :key=\"column.id\"\n            class=\"capitalize\"\n            :model-value=\"column.getIsVisible()\"\n            @update:model-value=\"(value) => {\n              column.toggleVisibility(!!value)\n            }\"\n          >\n            {{ column.id }}\n          \u003C\u002FDropdownMenuCheckboxItem>\n        \u003C\u002FDropdownMenuContent>\n      \u003C\u002FDropdownMenu>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C\u002FTable>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nThis adds a dropdown menu that you can use to toggle column visibility.\n\n\u003C\u002FSteps>\n\n## Row Selection\n\nNext, we're going to add row selection to our table. The `rowSelectionFeature` is already registered in our features object, so the table tracks selection for us — we just render the checkboxes.\n\n\u003CSteps>\n\n### Update column definitions\n\n```ts showLineNumbers {2,5-19}\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nimport { Checkbox } from '@\u002Fcomponents\u002Fui\u002Fcheckbox'\n\nexport const columns = columnHelper.columns([\n  columnHelper.display({\n    id: 'select',\n    header: ({ table }) => h(Checkbox, {\n      'modelValue': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),\n      'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),\n      'ariaLabel': 'Select all',\n    }),\n    cell: ({ row }) => h(Checkbox, {\n      'modelValue': row.getIsSelected(),\n      'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),\n      'ariaLabel': 'Select row',\n    }),\n    enableSorting: false,\n    enableHiding: false,\n  }),\n  \u002F\u002F ...\n])\n```\n\nThis adds a checkbox to each row and a checkbox in the header to select all rows.\n\n::callout{class=\"mt-4\"}\n\n**Note:** In v9, `table.getIsSomePageRowsSelected()` returns `true` whenever at least one page row is selected — even when all are. The header checkbox still works because `table.getIsAllPageRowsSelected() || ...` short-circuits to `true` at full selection before the `'indeterminate'` branch is reached.\n\n::\n\n### Manage the selection state externally\n\nThe table tracks selection internally by default. To show how a slice can live outside the table — handy when your page needs to read or drive the selection — we'll own just this one slice with a Vue `ref` and leave everything else internal. Expose the current value with a `state` getter, and resolve the updater in the matching callback:\n\n```ts showLineNumbers {2-3,5-7,13-18}\n\u002F\u002F components\u002Fpayments\u002Fdata-table.vue\nimport type { RowSelectionState } from '@tanstack\u002Fvue-table'\nimport { ref } from 'vue'\n\n\u002F\u002F Keep row selection outside the table so the rest of the app can read or update it.\nconst rowSelection = ref\u003CRowSelectionState>({})\n\nconst table = useTable({\n  features,\n  get data() { return props.data },\n  get columns() { return props.columns },\n  state: {\n    get rowSelection() { return rowSelection.value },\n  },\n  onRowSelectionChange: (updater) => {\n    rowSelection.value = typeof updater === 'function' ? updater(rowSelection.value) : updater\n  },\n})\n```\n\n`rowSelection.value` can now be read or written anywhere in the component, and the table stays in sync.\n\n### Show selected rows\n\nYou can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.\n\n```vue showLineNumbers {8-11}\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n        \u003CTable \u002F>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"flex items-center justify-end space-x-2 py-4\">\n      \u003Cdiv class=\"flex-1 text-sm text-muted-foreground\">\n        {{ table.getFilteredSelectedRowModel().rows.length }} of\n        {{ table.getFilteredRowModel().rows.length }} row(s) selected.\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"space-x-2\">\n        \u003CPaginationButtons \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n\u003C\u002FSteps>\n\n## Expanding\n\nLet's make rows expandable. The `rowExpandingFeature` and expanded row model are already registered in our features object, so the table tracks expanded state for us — we just add the UI.\n\n\u003CSteps>\n\n### Update `\u003CDataTable>`\n\nUpdate the `\u003CTableBody>` to render an extra row whenever a row is expanded:\n\n```vue showLineNumbers {3,9-13}\n\u003CTableBody>\n  \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n    \u003Ctemplate v-for=\"row in table.getRowModel().rows\" :key=\"row.id\">\n      \u003CTableRow :data-state=\"row.getIsSelected() && 'selected'\">\n        \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n          \u003CFlexRender :cell=\"cell\" \u002F>\n        \u003C\u002FTableCell>\n      \u003C\u002FTableRow>\n      \u003CTableRow v-if=\"row.getIsExpanded()\">\n        \u003CTableCell :colspan=\"row.getAllCells().length\">\n          {{ JSON.stringify(row.original) }}\n        \u003C\u002FTableCell>\n      \u003C\u002FTableRow>\n    \u003C\u002Ftemplate>\n  \u003C\u002Ftemplate>\n  \u003Ctemplate v-else>\n    \u003CTableRow>\n      \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n        No results.\n      \u003C\u002FTableCell>\n    \u003C\u002FTableRow>\n  \u003C\u002Ftemplate>\n\u003C\u002FTableBody>\n```\n\n### Add the expand action to the `DataTableDropDown.vue` component\n\n```vue showLineNumbers {12-14,34-36}\n\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from '@lucide\u002Fvue'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\ndefineEmits\u003C{\n  (e: 'expand'): void\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton variant=\"ghost\" class=\"w-8 h-8 p-0\">\n        \u003Cspan class=\"sr-only\">Open menu\u003C\u002Fspan>\n        \u003CMoreHorizontal class=\"w-4 h-4\" \u002F>\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem @click=\"$emit('expand')\">\n        Expand\n      \u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuSeparator \u002F>\n      \u003CDropdownMenuItem>View customer\u003C\u002FDropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C\u002FDropdownMenuItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n```\n\n### Make rows expandable\n\nNow we can update the action cell to add the expand control.\n\n```ts showLineNumbers {12}\n\u002F\u002F components\u002Fpayments\u002Fcolumns.ts\nexport const columns = columnHelper.columns([\n  \u002F\u002F ...\n  columnHelper.display({\n    id: 'actions',\n    enableHiding: false,\n    cell: ({ row }) => {\n      const payment = row.original\n\n      return h('div', { class: 'relative' }, h(DropdownAction, {\n        payment,\n        onExpand: () => row.toggleExpanded(),\n      }))\n    },\n  }),\n])\n```\n\n\u003C\u002FSteps>\n\n## Reusable Components\n\nHere are some components you can use to build your data tables. These examples are adapted from the Tasks demo.\n\n### Column header\n\nMake any column header sortable and hideable.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport type { Column } from '@tanstack\u002Fvue-table'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport ArrowDownIcon from '~icons\u002Fradix-icons\u002Farrow-down'\nimport ArrowUpIcon from '~icons\u002Fradix-icons\u002Farrow-up'\nimport CaretSortIcon from '~icons\u002Fradix-icons\u002Fcaret-sort'\nimport EyeNoneIcon from '~icons\u002Fradix-icons\u002Feye-none'\n\nimport { cn } from '@\u002Flib\u002Futils'\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ninterface DataTableColumnHeaderProps {\n  column: Column\u003CDataTableFeatures, Task>\n  title: string\n}\n\ndefineProps\u003CDataTableColumnHeaderProps>()\n\u003C\u002Fscript>\n\n\u003Cscript lang=\"ts\">\nexport default {\n  inheritAttrs: false,\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"column.getCanSort()\" :class=\"cn('flex items-center space-x-2', $attrs.class ?? '')\">\n    \u003CDropdownMenu>\n      \u003CDropdownMenuTrigger as-child>\n        \u003CButton\n          variant=\"ghost\"\n          size=\"sm\"\n          class=\"-ml-3 h-8 data-[state=open]:bg-accent\"\n        >\n          \u003Cspan>{{ title }}\u003C\u002Fspan>\n          \u003CArrowDownIcon v-if=\"column.getIsSorted() === 'desc'\" class=\"w-4 h-4 ml-2\" \u002F>\n          \u003CArrowUpIcon v-else-if=\" column.getIsSorted() === 'asc'\" class=\"w-4 h-4 ml-2\" \u002F>\n          \u003CCaretSortIcon v-else class=\"w-4 h-4 ml-2\" \u002F>\n        \u003C\u002FButton>\n      \u003C\u002FDropdownMenuTrigger>\n      \u003CDropdownMenuContent align=\"start\">\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(false)\">\n          \u003CArrowUpIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Asc\n        \u003C\u002FDropdownMenuItem>\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(true)\">\n          \u003CArrowDownIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Desc\n        \u003C\u002FDropdownMenuItem>\n        \u003CDropdownMenuSeparator \u002F>\n        \u003CDropdownMenuItem @click=\"column.toggleVisibility(false)\">\n          \u003CEyeNoneIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground\u002F70\" \u002F>\n          Hide\n        \u003C\u002FDropdownMenuItem>\n      \u003C\u002FDropdownMenuContent>\n    \u003C\u002FDropdownMenu>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv v-else :class=\"$attrs.class\">\n    {{ title }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n```\n\n```ts showLineNumbers\nexport const columns = columnHelper.columns([\n  columnHelper.accessor('email', {\n    header: ({ column }) => (\n      h(DataTableColumnHeader, {\n        column,\n        title: 'Email',\n      })\n    ),\n  }),\n])\n```\n\n### Pagination\n\nAdd pagination controls to your table including page size and selection count.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport { type Table } from '@tanstack\u002Fvue-table'\nimport { computed } from 'vue'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport ChevronLeftIcon from '~icons\u002Fradix-icons\u002Fchevron-left'\nimport ChevronRightIcon from '~icons\u002Fradix-icons\u002Fchevron-right'\nimport DoubleArrowLeftIcon from '~icons\u002Fradix-icons\u002Fdouble-arrow-left'\nimport DoubleArrowRightIcon from '~icons\u002Fradix-icons\u002Fdouble-arrow-right'\n\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@\u002Fcomponents\u002Fui\u002Fselect'\n\ninterface DataTablePaginationProps {\n  table: Table\u003CDataTableFeatures, Task>\n}\nconst props = defineProps\u003CDataTablePaginationProps>()\n\n\u002F\u002F Reactive atom read — a bare `.get()` in script setup is a one-time snapshot.\nconst pagination = computed(() => props.table.atoms.pagination.get())\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"flex items-center justify-between px-2\">\n    \u003Cdiv class=\"flex-1 text-sm text-muted-foreground\">\n      {{ table.getFilteredSelectedRowModel().rows.length }} of\n      {{ table.getFilteredRowModel().rows.length }} row(s) selected.\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"flex items-center space-x-6 lg:space-x-8\">\n      \u003Cdiv class=\"flex items-center space-x-2\">\n        \u003Cp class=\"text-sm font-medium\">\n          Rows per page\n        \u003C\u002Fp>\n        \u003CSelect\n          :model-value=\"`${pagination.pageSize}`\"\n          @update:model-value=\"table.setPageSize\"\n        >\n          \u003CSelectTrigger class=\"h-8 w-[70px]\">\n            \u003CSelectValue :placeholder=\"`${pagination.pageSize}`\" \u002F>\n          \u003C\u002FSelectTrigger>\n          \u003CSelectContent side=\"top\">\n            \u003CSelectItem v-for=\"pageSize in [10, 20, 30, 40, 50]\" :key=\"pageSize\" :value=\"`${pageSize}`\">\n              {{ pageSize }}\n            \u003C\u002FSelectItem>\n          \u003C\u002FSelectContent>\n        \u003C\u002FSelect>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"flex w-[100px] items-center justify-center text-sm font-medium\">\n        Page {{ pagination.pageIndex + 1 }} of\n        {{ table.getPageCount() }}\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"flex items-center space-x-2\">\n        \u003CButton\n          variant=\"outline\"\n          class=\"hidden w-8 h-8 p-0 lg:flex\"\n          :disabled=\"!table.getCanPreviousPage()\"\n          @click=\"table.setPageIndex(0)\"\n        >\n          \u003Cspan class=\"sr-only\">Go to first page\u003C\u002Fspan>\n          \u003CDoubleArrowLeftIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"w-8 h-8 p-0\"\n          :disabled=\"!table.getCanPreviousPage()\"\n          @click=\"table.previousPage()\"\n        >\n          \u003Cspan class=\"sr-only\">Go to previous page\u003C\u002Fspan>\n          \u003CChevronLeftIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"w-8 h-8 p-0\"\n          :disabled=\"!table.getCanNextPage()\"\n          @click=\"table.nextPage()\"\n        >\n          \u003Cspan class=\"sr-only\">Go to next page\u003C\u002Fspan>\n          \u003CChevronRightIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n        \u003CButton\n          variant=\"outline\"\n          class=\"hidden w-8 h-8 p-0 lg:flex\"\n          :disabled=\"!table.getCanNextPage()\"\n          @click=\"table.setPageIndex(table.getPageCount() - 1)\"\n        >\n          \u003Cspan class=\"sr-only\">Go to last page\u003C\u002Fspan>\n          \u003CDoubleArrowRightIcon class=\"w-4 h-4\" \u002F>\n        \u003C\u002FButton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n```\n\n```vue\n\u003CDataTablePagination :table=\"table\" \u002F>\n```\n\n### Column toggle\n\nA component to toggle column visibility.\n\n```vue showLineNumbers\n\u003Cscript setup lang=\"ts\">\nimport type { Table } from '@tanstack\u002Fvue-table'\nimport { computed } from 'vue'\nimport { type Task } from '..\u002Fdata\u002Fschema'\nimport type { DataTableFeatures } from '..\u002Ffeatures'\nimport MixerHorizontalIcon from '~icons\u002Fradix-icons\u002Fmixer-horizontal'\n\nimport { Button } from '@\u002Fcomponents\u002Fui\u002Fbutton'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@\u002Fcomponents\u002Fui\u002Fdropdown-menu'\n\ninterface DataTableViewOptionsProps {\n  table: Table\u003CDataTableFeatures, Task>\n}\n\nconst props = defineProps\u003CDataTableViewOptionsProps>()\n\nconst columns = computed(() => props.table.getAllColumns()\n  .filter(\n    column =>\n      typeof column.accessorFn !== 'undefined' && column.getCanHide(),\n  ))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu>\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        class=\"hidden h-8 ml-auto lg:flex\"\n      >\n        \u003CMixerHorizontalIcon class=\"w-4 h-4 mr-2\" \u002F>\n        View\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\" class=\"w-[150px]\">\n      \u003CDropdownMenuLabel>Toggle columns\u003C\u002FDropdownMenuLabel>\n      \u003CDropdownMenuSeparator \u002F>\n\n      \u003CDropdownMenuCheckboxItem\n        v-for=\"column in columns\"\n        :key=\"column.id\"\n        class=\"capitalize\"\n        :modelValue=\"column.getIsVisible()\"\n        @update:modelValue=\"(value) => column.toggleVisibility(!!value)\"\n      >\n        {{ column.id }}\n      \u003C\u002FDropdownMenuCheckboxItem>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n```\n",{"title":97,"description":1529},"wguKCjQH1nmyx1-apTVzMzhXR9voLUv8v7JVahhxkvE",[1537,1538],{"title":93,"path":94,"stem":95,"children":-1},{"title":101,"path":102,"stem":103,"children":-1},1788943233104]