[{"data":1,"prerenderedAt":1311},["ShallowReactive",2],{"navigation":3,"$fMW9jOOkNpZriWVK3P_bZYNOyYR2IEPvrHsC7cDBlJJc":405,"/docs/components/data-table":418,"surround-/docs/components/data-table":1308},[4],{"title":5,"path":6,"stem":7,"children":8,"page":17},"Docs","/docs","docs",[9,90,353,360,366,379],{"path":6,"stem":7,"title":10,"type":11,"children":12},"Get Started","group",[13,19,44,48,52,70,74,78,82,86],{"title":14,"path":15,"stem":16,"new":17,"type":18,"children":-1},"Introduction","/docs/introduction","docs/01.introduction",false,"page",{"title":20,"path":21,"stem":22,"children":23,"new":17,"type":11},"Installation","/docs/installation","docs/02.installation",[24,28,32,36,40],{"title":25,"path":26,"stem":27,"new":17,"type":18,"children":-1},"Vite","/docs/installation/vite","docs/installation/01.vite",{"title":29,"path":30,"stem":31,"new":17,"type":18,"children":-1},"Nuxt","/docs/installation/nuxt","docs/installation/02.nuxt",{"title":33,"path":34,"stem":35,"new":17,"type":18,"children":-1},"Astro","/docs/installation/astro","docs/installation/03.astro",{"title":37,"path":38,"stem":39,"new":17,"type":18,"children":-1},"Laravel","/docs/installation/laravel","docs/installation/04.laravel",{"title":41,"path":42,"stem":43,"new":17,"type":18,"children":-1},"Manual Installation","/docs/installation/manual","docs/installation/05.manual",{"title":45,"path":46,"stem":47,"new":17,"type":18,"children":-1},"components.json","/docs/components-json","docs/03.components-json",{"title":49,"path":50,"stem":51,"new":17,"type":18,"children":-1},"Theming","/docs/theming","docs/04.theming",{"title":53,"path":54,"stem":55,"children":56,"new":17,"type":11},"Dark Mode","/docs/dark-mode","docs/05.dark-mode",[57,60,63,67],{"title":25,"path":58,"stem":59,"new":17,"type":18,"children":-1},"/docs/dark-mode/vite","docs/dark-mode/01.vite",{"title":29,"path":61,"stem":62,"new":17,"type":18,"children":-1},"/docs/dark-mode/nuxt","docs/dark-mode/02.nuxt",{"title":64,"path":65,"stem":66,"new":17,"type":18,"children":-1},"Vitepress","/docs/dark-mode/vitepress","docs/dark-mode/03.vitepress",{"title":33,"path":68,"stem":69,"new":17,"type":18,"children":-1},"/docs/dark-mode/astro","docs/dark-mode/04.astro",{"title":71,"path":72,"stem":73,"new":17,"type":18,"children":-1},"CLI","/docs/cli","docs/06.cli",{"title":75,"path":76,"stem":77,"new":17,"type":18,"children":-1},"JavaScript","/docs/javascript","docs/07.javascript",{"title":79,"path":80,"stem":81,"new":17,"type":18,"children":-1},"Figma","/docs/figma","docs/09.figma",{"title":83,"path":84,"stem":85,"new":17,"type":18,"children":-1},"Changelog","/docs/changelog","docs/10.changelog",{"title":87,"path":88,"stem":89,"new":17,"type":18,"children":-1},"Legacy Docs","/docs/legacy","docs/11.legacy",{"title":91,"path":92,"stem":93,"children":94,"new":17,"type":11},"Components","/docs/components","docs/02.components",[95,100,104,108,112,116,120,124,128,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,193,197,201,205,209,213,217,221,225,229,233,237,241,245,249,253,257,261,265,269,273,277,281,285,289,293,297,301,305,309,313,317,321,325,329,333,337,341,345,349],{"title":96,"path":97,"stem":98,"new":17,"type":99,"children":-1},"Accordion","/docs/components/accordion","docs/components/accordion","component",{"title":101,"path":102,"stem":103,"new":17,"type":99,"children":-1},"Alert","/docs/components/alert","docs/components/alert",{"title":105,"path":106,"stem":107,"new":17,"type":99,"children":-1},"Alert Dialog","/docs/components/alert-dialog","docs/components/alert-dialog",{"title":109,"path":110,"stem":111,"new":17,"type":99,"children":-1},"Aspect Ratio","/docs/components/aspect-ratio","docs/components/aspect-ratio",{"title":113,"path":114,"stem":115,"new":17,"type":99,"children":-1},"Avatar","/docs/components/avatar","docs/components/avatar",{"title":117,"path":118,"stem":119,"new":17,"type":99,"children":-1},"Badge","/docs/components/badge","docs/components/badge",{"title":121,"path":122,"stem":123,"new":17,"type":99,"children":-1},"Breadcrumb","/docs/components/breadcrumb","docs/components/breadcrumb",{"title":125,"path":126,"stem":127,"new":17,"type":99,"children":-1},"Button","/docs/components/button","docs/components/button",{"title":129,"path":130,"stem":131,"new":132,"type":99,"children":-1},"Button Group","/docs/components/button-group","docs/components/button-group",true,{"title":134,"path":135,"stem":136,"new":17,"type":99,"children":-1},"Calendar","/docs/components/calendar","docs/components/calendar",{"title":138,"path":139,"stem":140,"new":17,"type":99,"children":-1},"Card","/docs/components/card","docs/components/card",{"title":142,"path":143,"stem":144,"new":17,"type":99,"children":-1},"Carousel","/docs/components/carousel","docs/components/carousel",{"title":146,"path":147,"stem":148,"new":17,"type":99,"children":-1},"Chart","/docs/components/chart","docs/components/chart",{"title":150,"path":151,"stem":152,"new":17,"type":99,"children":-1},"Checkbox","/docs/components/checkbox","docs/components/checkbox",{"title":154,"path":155,"stem":156,"new":17,"type":99,"children":-1},"Collapsible","/docs/components/collapsible","docs/components/collapsible",{"title":158,"path":159,"stem":160,"new":17,"type":99,"children":-1},"Combobox","/docs/components/combobox","docs/components/combobox",{"title":162,"path":163,"stem":164,"new":17,"type":99,"children":-1},"Command","/docs/components/command","docs/components/command",{"title":166,"path":167,"stem":168,"new":17,"type":99,"children":-1},"Context Menu","/docs/components/context-menu","docs/components/context-menu",{"title":170,"path":171,"stem":172,"new":17,"type":99,"children":-1},"Data Table","/docs/components/data-table","docs/components/data-table",{"title":174,"path":175,"stem":176,"new":17,"type":99,"children":-1},"Date Picker","/docs/components/date-picker","docs/components/date-picker",{"title":178,"path":179,"stem":180,"new":17,"type":99,"children":-1},"Dialog","/docs/components/dialog","docs/components/dialog",{"title":182,"path":183,"stem":184,"new":17,"type":99,"children":-1},"Drawer","/docs/components/drawer","docs/components/drawer",{"title":186,"path":187,"stem":188,"new":17,"type":99,"children":-1},"Dropdown Menu","/docs/components/dropdown-menu","docs/components/dropdown-menu",{"title":190,"path":191,"stem":192,"new":132,"type":99,"children":-1},"Empty","/docs/components/empty","docs/components/empty",{"title":194,"path":195,"stem":196,"new":132,"type":99,"children":-1},"Field","/docs/components/field","docs/components/field",{"title":198,"path":199,"stem":200,"new":17,"type":99,"children":-1},"Form","/docs/components/form","docs/components/form",{"title":202,"path":203,"stem":204,"new":17,"type":99,"children":-1},"Hover Card","/docs/components/hover-card","docs/components/hover-card",{"title":206,"path":207,"stem":208,"new":17,"type":99,"children":-1},"Input","/docs/components/input","docs/components/input",{"title":210,"path":211,"stem":212,"new":132,"type":99,"children":-1},"Input Group","/docs/components/input-group","docs/components/input-group",{"title":214,"path":215,"stem":216,"new":17,"type":99,"children":-1},"Input OTP","/docs/components/input-otp","docs/components/input-otp",{"title":218,"path":219,"stem":220,"new":132,"type":99,"children":-1},"Item","/docs/components/item","docs/components/item",{"title":222,"path":223,"stem":224,"new":132,"type":99,"children":-1},"Kbd","/docs/components/kbd","docs/components/kbd",{"title":226,"path":227,"stem":228,"new":17,"type":99,"children":-1},"Label","/docs/components/label","docs/components/label",{"title":230,"path":231,"stem":232,"new":17,"type":99,"children":-1},"Menubar","/docs/components/menubar","docs/components/menubar",{"title":234,"path":235,"stem":236,"new":132,"type":99,"children":-1},"Native Select","/docs/components/native-select","docs/components/native-select",{"title":238,"path":239,"stem":240,"new":17,"type":99,"children":-1},"Navigation Menu","/docs/components/navigation-menu","docs/components/navigation-menu",{"title":242,"path":243,"stem":244,"new":17,"type":99,"children":-1},"Number Field","/docs/components/number-field","docs/components/number-field",{"title":246,"path":247,"stem":248,"new":17,"type":99,"children":-1},"Pagination","/docs/components/pagination","docs/components/pagination",{"title":250,"path":251,"stem":252,"new":17,"type":99,"children":-1},"Pin Input","/docs/components/pin-input","docs/components/pin-input",{"title":254,"path":255,"stem":256,"new":17,"type":99,"children":-1},"Popover","/docs/components/popover","docs/components/popover",{"title":258,"path":259,"stem":260,"new":17,"type":99,"children":-1},"Progress","/docs/components/progress","docs/components/progress",{"title":262,"path":263,"stem":264,"new":17,"type":99,"children":-1},"Radio Group","/docs/components/radio-group","docs/components/radio-group",{"title":266,"path":267,"stem":268,"new":17,"type":99,"children":-1},"Range Calendar","/docs/components/range-calendar","docs/components/range-calendar",{"title":270,"path":271,"stem":272,"new":17,"type":99,"children":-1},"Resizable","/docs/components/resizable","docs/components/resizable",{"title":274,"path":275,"stem":276,"new":17,"type":99,"children":-1},"Scroll Area","/docs/components/scroll-area","docs/components/scroll-area",{"title":278,"path":279,"stem":280,"new":17,"type":99,"children":-1},"Select","/docs/components/select","docs/components/select",{"title":282,"path":283,"stem":284,"new":17,"type":99,"children":-1},"Separator","/docs/components/separator","docs/components/separator",{"title":286,"path":287,"stem":288,"new":17,"type":99,"children":-1},"Sheet","/docs/components/sheet","docs/components/sheet",{"title":290,"path":291,"stem":292,"new":17,"type":99,"children":-1},"Sidebar","/docs/components/sidebar","docs/components/sidebar",{"title":294,"path":295,"stem":296,"new":17,"type":99,"children":-1},"Skeleton","/docs/components/skeleton","docs/components/skeleton",{"title":298,"path":299,"stem":300,"new":17,"type":99,"children":-1},"Slider","/docs/components/slider","docs/components/slider",{"title":302,"path":303,"stem":304,"new":17,"type":99,"children":-1},"Sonner","/docs/components/sonner","docs/components/sonner",{"title":306,"path":307,"stem":308,"new":132,"type":99,"children":-1},"Spinner","/docs/components/spinner","docs/components/spinner",{"title":310,"path":311,"stem":312,"new":17,"type":99,"children":-1},"Stepper","/docs/components/stepper","docs/components/stepper",{"title":314,"path":315,"stem":316,"new":17,"type":99,"children":-1},"Switch","/docs/components/switch","docs/components/switch",{"title":318,"path":319,"stem":320,"new":17,"type":99,"children":-1},"Table","/docs/components/table","docs/components/table",{"title":322,"path":323,"stem":324,"new":17,"type":99,"children":-1},"Tabs","/docs/components/tabs","docs/components/tabs",{"title":326,"path":327,"stem":328,"new":17,"type":99,"children":-1},"Tags Input","/docs/components/tags-input","docs/components/tags-input",{"title":330,"path":331,"stem":332,"new":17,"type":99,"children":-1},"Textarea","/docs/components/textarea","docs/components/textarea",{"title":334,"path":335,"stem":336,"new":17,"type":99,"children":-1},"Toast","/docs/components/toast","docs/components/toast",{"title":338,"path":339,"stem":340,"new":17,"type":99,"children":-1},"Toggle","/docs/components/toggle","docs/components/toggle",{"title":342,"path":343,"stem":344,"new":17,"type":99,"children":-1},"Toggle Group","/docs/components/toggle-group","docs/components/toggle-group",{"title":346,"path":347,"stem":348,"new":17,"type":99,"children":-1},"Tooltip","/docs/components/tooltip","docs/components/tooltip",{"title":350,"path":351,"stem":352,"new":17,"type":99,"children":-1},"Typography","/docs/components/typography","docs/components/typography",{"title":20,"path":21,"stem":22,"children":354,"new":17,"type":11},[355,356,357,358,359],{"title":25,"path":26,"stem":27,"new":17,"type":18,"children":-1},{"title":29,"path":30,"stem":31,"new":17,"type":18,"children":-1},{"title":33,"path":34,"stem":35,"new":17,"type":18,"children":-1},{"title":37,"path":38,"stem":39,"new":17,"type":18,"children":-1},{"title":41,"path":42,"stem":43,"new":17,"type":18,"children":-1},{"title":53,"path":54,"stem":55,"children":361,"new":17,"type":11},[362,363,364,365],{"title":25,"path":58,"stem":59,"new":17,"type":18,"children":-1},{"title":29,"path":61,"stem":62,"new":17,"type":18,"children":-1},{"title":64,"path":65,"stem":66,"new":17,"type":18,"children":-1},{"title":33,"path":68,"stem":69,"new":17,"type":18,"children":-1},{"title":367,"path":368,"stem":369,"children":370,"new":17,"type":11},"Forms","/docs/forms","docs/forms",[371,375],{"title":372,"path":373,"stem":374,"new":17,"type":18,"children":-1},"VeeValidate","/docs/forms/vee-validate","docs/forms/01.vee-validate",{"title":376,"path":377,"stem":378,"new":17,"type":18,"children":-1},"TanStack Form","/docs/forms/tanstack-form","docs/forms/02.tanstack-form",{"title":380,"path":381,"stem":382,"children":383,"new":17,"type":11},"Registry","/docs/registry","docs/registry/index",[384,385,389,393,397,401],{"title":380,"path":381,"stem":382,"new":17,"type":18,"children":-1},{"title":386,"path":387,"stem":388,"new":17,"type":18,"children":-1},"Examples","/docs/registry/examples","docs/registry/examples",{"title":390,"path":391,"stem":392,"new":17,"type":18,"children":-1},"FAQ","/docs/registry/faq","docs/registry/faq",{"title":394,"path":395,"stem":396,"new":17,"type":18,"children":-1},"Getting Started","/docs/registry/getting-started","docs/registry/getting-started",{"title":398,"path":399,"stem":400,"new":17,"type":18,"children":-1},"registry-item.json","/docs/registry/registry-item-json","docs/registry/registry-item-json",{"title":402,"path":403,"stem":404,"new":17,"type":18,"children":-1},"registry.json","/docs/registry/registry-json","docs/registry/registry-json",{"repo":406},{"id":407,"name":408,"repo":409,"description":410,"createdAt":411,"updatedAt":412,"pushedAt":413,"stars":414,"watchers":415,"forks":416,"defaultBranch":417},658791894,"shadcn-vue","unovue/shadcn-vue","Vue port of shadcn-ui","2023-06-26T13:53:23Z","2026-04-06T17:33:41Z","2026-04-06T17:33:39Z",9706,37,634,"dev",{"id":419,"title":170,"body":420,"description":1301,"extension":1302,"links":1303,"meta":1304,"navigation":132,"new":17,"path":171,"rawbody":1305,"seo":1306,"stem":172,"__hash__":1307},"content/docs/components/data-table.md",{"type":421,"value":422,"toc":1274},"minimark",[423,429,440,444,447,458,461,469,481,485,497,548,551,560,570,580,586,590,593,602,606,609,617,620,652,655,658,740,744,747],[424,425],"component-preview",{"align":426,"description":427,"name":428},"start","A data table with sorting, filtering, and pagination.","DataTableDemo",[430,431,436],"vue-school-link",{"className":432,"lesson":434,"placement":435},[433],"mt-6","data-tables-and-sonner-in-shadcn-vue","top",[437,438,439],"p",{},"Watch a Vue School video about data tables in shadcn-vue.",[441,442,14],"h2",{"id":443},"introduction",[437,445,446],{},"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.",[437,448,449,450,457],{},"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 ",[451,452,456],"a",{"href":453,"rel":454},"https://tanstack.com/table/v8/docs/introduction#what-is-headless-ui",[455],"nofollow","headless UI"," provides.",[437,459,460],{},"So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.",[437,462,463,464,468],{},"We'll start with the basic ",[465,466,467],"code",{},"\u003CTable />"," component and build a complex data table from scratch.",[470,471,474],"callout",{"className":472},[473],"mt-4",[437,475,476,480],{},[477,478,479],"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.",[441,482,484],{"id":483},"table-of-contents","Table of Contents",[437,486,487,488,493,494,496],{},"This guide will show you how to use ",[451,489,492],{"href":490,"rel":491},"https://tanstack.com/table",[455],"TanStack Table"," and the ",[465,495,467],{}," component to build your own custom data table. We'll cover the following topics:",[498,499,500,507,513,518,524,530,536,542],"ul",{},[501,502,503],"li",{},[451,504,506],{"href":505},"#basic-table","Basic Table",[501,508,509],{},[451,510,512],{"href":511},"#row-actions","Row Actions",[501,514,515],{},[451,516,246],{"href":517},"#pagination",[501,519,520],{},[451,521,523],{"href":522},"#sorting","Sorting",[501,525,526],{},[451,527,529],{"href":528},"#filtering","Filtering",[501,531,532],{},[451,533,535],{"href":534},"#visibility","Visibility",[501,537,538],{},[451,539,541],{"href":540},"#row-selection","Row Selection",[501,543,544],{},[451,545,547],{"href":546},"#reusable-components","Reusable Components",[441,549,20],{"id":550},"installation",[552,553,554],"ol",{},[501,555,556,557,559],{},"Add the ",[465,558,467],{}," component to your project:",[561,562,568],"pre",{"className":563,"code":565,"language":566,"meta":567},[564],"language-bash","npx shadcn-vue@latest add table\n","bash","",[465,569,565],{"__ignoreMap":567},[552,571,573],{"start":572},2,[501,574,575,576,579],{},"Add ",[465,577,578],{},"tanstack/vue-table"," dependency:",[561,581,584],{"className":582,"code":583,"language":566,"meta":567},[564],"npm install @tanstack/vue-table\n",[465,585,583],{"__ignoreMap":567},[441,587,589],{"id":588},"prerequisites","Prerequisites",[437,591,592],{},"We are going to build a table to show recent payments. Here's what our data looks like:",[561,594,600],{"className":595,"code":597,"language":598,"meta":599},[596],"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  // ...\n]\n","ts","showLineNumbers",[465,601,597],{"__ignoreMap":567},[441,603,605],{"id":604},"project-structure","Project Structure",[437,607,608],{},"Start by creating the following file structure:",[561,610,615],{"className":611,"code":613,"language":614,"meta":567},[612],"language-ansi"," components\n    └── payments\n          ├── columns.ts\n          ├── data-table.vue\n          ├── data-table-dropdown.vue\n└── app.vue\n","ansi",[465,616,613],{"__ignoreMap":567},[437,618,619],{},"I'm using a Nuxt example here but this works for any other Vue framework.",[498,621,622,628,638,646],{},[501,623,624,627],{},[465,625,626],{},"columns.ts"," It will contain our column definitions.",[501,629,630,633,634,637],{},[465,631,632],{},"data-table.vue"," It will contain our ",[465,635,636],{},"\u003CDataTable />"," component.",[501,639,640,633,643,637],{},[465,641,642],{},"data-table-dropdown.vue",[465,644,645],{},"\u003CDropdownAction />",[501,647,648,651],{},[465,649,650],{},"app.vue"," This is where we'll fetch data and render our table.",[441,653,506],{"id":654},"basic-table",[437,656,657],{},"Let's start by building a basic table.",[659,660,661,666,672,678,687,693,699,707,727,731,734],"steps",{},[662,663,665],"h3",{"id":664},"column-definitions","Column Definitions",[437,667,668,669,671],{},"First, we'll define our columns in the ",[465,670,626],{}," file.",[561,673,676],{"className":674,"code":675,"language":598,"meta":599},[596],"import { h } from 'vue'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n  {\n    accessorKey: 'amount',\n    header: () => h('div', { class: 'text-right' }, 'Amount'),\n    cell: ({ row }) => {\n      const amount = Number.parseFloat(row.getValue('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",[465,677,675],{"__ignoreMap":567},[470,679,681],{"className":680},[473],[437,682,683,686],{},[477,684,685],{},"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.",[662,688,690,692],{"id":689},"datatable-component",[465,691,636],{}," component",[437,694,695,696,698],{},"Next, we'll create a ",[465,697,636],{}," component to render our table.",[561,700,705],{"className":701,"code":703,"language":704,"meta":567},[702],"language-vue","\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type { ColumnDef } from '@tanstack/vue-table'\nimport {\n  FlexRender,\n  getCoreRowModel,\n  useVueTable,\n} from '@tanstack/vue-table'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CTData, TValue>[]\n  data: TData[]\n}>()\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n})\n\u003C/script>\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\n              v-if=\"!header.isPlaceholder\" :render=\"header.column.columnDef.header\"\n              :props=\"header.getContext()\"\n            />\n          \u003C/TableHead>\n        \u003C/TableRow>\n      \u003C/TableHeader>\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' : undefined\"\n          >\n            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n              \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n            \u003C/TableCell>\n          \u003C/TableRow>\n        \u003C/template>\n        \u003Ctemplate v-else>\n          \u003CTableRow>\n            \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n              No results.\n            \u003C/TableCell>\n          \u003C/TableRow>\n        \u003C/template>\n      \u003C/TableBody>\n    \u003C/Table>\n  \u003C/div>\n\u003C/template>\n","vue",[465,706,703],{"__ignoreMap":567},[470,708,709,722],{},[437,710,711,714,715,717,718,721],{},[477,712,713],{},"Tip",": If you find yourself using ",[465,716,636],{}," in multiple places, this is the component you could make reusable by extracting it to ",[465,719,720],{},"components/ui/data-table.vue",".",[437,723,724],{},[465,725,726],{},"\u003CDataTable :columns=\"columns\" :data=\"data\" />",[662,728,730],{"id":729},"render-the-table","Render the table",[437,732,733],{},"Finally, we'll render our table in our index component.",[561,735,738],{"className":736,"code":737,"language":704,"meta":567},[702],"\u003Cscript setup lang=\"ts\">\nimport type { Payment } from './components/columns'\nimport { onMounted, ref } from 'vue'\nimport { columns } from './components/columns'\nimport DataTable from './components/DataTable.vue'\n\nconst data = ref\u003CPayment[]>([])\n\nasync function getData(): Promise\u003CPayment[]> {\n  // 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    // ...\n  ]\n}\n\nonMounted(async () => {\n  data.value = await getData()\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"container py-10 mx-auto\">\n    \u003CDataTable :columns=\"columns\" :data=\"data\" />\n  \u003C/div>\n\u003C/template>\n",[465,739,737],{"__ignoreMap":567},[441,741,743],{"id":742},"cell-formatting","Cell Formatting",[437,745,746],{},"Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.",[659,748,749,753,764,769,772,775,782,832,835,838,896,899,902,998,1001,1004,1040,1043,1050,1087,1090,1093,1146,1213,1216,1224,1228,1231,1237,1243,1246,1249,1255,1261,1265,1268],{},[662,750,752],{"id":751},"update-columns-definition","Update columns definition",[437,754,755,756,759,760,763],{},"Update the ",[465,757,758],{},"header"," and ",[465,761,762],{},"cell"," definitions for amount as follows:",[561,765,767],{"className":766,"code":675,"language":598,"meta":567},[596],[465,768,675],{"__ignoreMap":567},[437,770,771],{},"You can use the same approach to format other cells and headers.\n",[441,773,512],{"id":774},"row-actions",[437,776,777,778,781],{},"Let's add row actions to our table. We'll use a ",[465,779,780],{},"\u003CDropdown />"," component for this.",[659,783,784,791,797,800,812,818],{},[662,785,787,788,692],{"id":786},"add-the-following-into-your-datatabledropdownvue-component","Add the following into your ",[465,789,790],{},"DataTableDropDown.vue",[561,792,795],{"className":793,"code":794,"language":704,"meta":567},[702],"\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from 'lucide-vue-next'\nimport { Button } from '@/components/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C/script>\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/span>\n        \u003CMoreHorizontal class=\"w-4 h-4\" />\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuSeparator />\n      \u003CDropdownMenuItem>View customer\u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C/DropdownMenuItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n",[465,796,794],{"__ignoreMap":567},[662,798,752],{"id":799},"update-columns-definition-1",[437,801,802,803,806,807,809,810,637],{},"Update our columns definition to add a new ",[465,804,805],{},"actions"," column. The ",[465,808,805],{}," cell returns a ",[465,811,780],{},[561,813,816],{"className":814,"code":815,"language":598,"meta":567},[596],"import { ColumnDef } from '@tanstack/vue-table'\nimport DropdownAction from '@/components/DataTableDropDown.vue'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n  // ...\n  {\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",[465,817,815],{"__ignoreMap":567},[437,819,820,821,824,825,827,828,831],{},"You can access the row data using ",[465,822,823],{},"row.original"," in the ",[465,826,762],{}," function. Use this to handle actions for your row eg. use the ",[465,829,830],{},"id"," to make a DELETE call to your API.",[441,833,246],{"id":834},"pagination",[437,836,837],{},"Next, we'll add pagination to our table.",[659,839,840,847,856,865,869,884,890],{},[662,841,843,844],{"id":842},"update-datatable","Update ",[465,845,846],{},"\u003CDataTable>",[561,848,854],{"className":849,"code":850,"highlights":851,"language":598,"meta":599},[596],"import {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n})\n",[852,853],4,12,[465,855,850],{"__ignoreMap":567},[437,857,858,859,864],{},"This will automatically paginate your rows into pages of 10. See the ",[451,860,863],{"href":861,"rel":862},"https://tanstack.com/table/v8/docs/api/features/pagination",[455],"pagination docs"," for more information on customizing page size and implementing manual pagination.",[662,866,868],{"id":867},"add-pagination-controls","Add pagination controls",[437,870,871,872,875,876,879,880,883],{},"We can add pagination controls to our table using the ",[465,873,874],{},"\u003CButton />"," component and the ",[465,877,878],{},"table.previousPage()",", ",[465,881,882],{},"table.nextPage()"," API methods.",[561,885,888],{"className":886,"code":887,"language":704,"meta":567},[702],"\u003Cscript lang=\"ts\" generic=\"TData, TValue\">\nimport { Button } from '@/components/ui/button'\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n  getPaginationRowModel: getPaginationRowModel(),\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>\n        { // .... }\n      \u003C/Table>\n    \u003C/div>\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/Button>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanNextPage()\"\n        @click=\"table.nextPage()\"\n      >\n        Next\n      \u003C/Button>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n",[465,889,887],{"__ignoreMap":567},[437,891,892,893,895],{},"See ",[451,894,547],{"href":546}," section for a more advanced pagination component.",[441,897,523],{"id":898},"sorting",[437,900,901],{},"Let's make the email column sortable.",[659,903,904,911,917,922,937,971,975,982,995],{},[662,905,787,907,910],{"id":906},"add-the-following-into-your-utils-file",[465,908,909],{},"utils"," file",[561,912,915],{"className":913,"code":914,"language":598,"meta":567},[596],"import type { Updater } from '@tanstack/vue-table'\nimport type { ClassValue } from 'clsx'\n\nimport type { Ref } from 'vue'\nimport { clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n",[465,916,914],{"__ignoreMap":567},[662,918,843,920],{"id":919},"update-datatable-1",[465,921,846],{},[561,923,935],{"className":924,"code":925,"highlights":926,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  SortingState,\n} from '@tanstack/vue-table'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { h, ref } from 'vue'\n\nimport {\n  FlexRender,\n  getCoreRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useVueTable,\n} from '@tanstack/vue-table'\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CTData, TValue>[]\n  data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n  getPaginationRowModel: getPaginationRowModel(),\n  getSortedRowModel: getSortedRowModel(),\n  onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n  state: {\n    get sorting() { return sorting.value },\n  },\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C/Table>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n",[852,927,928,929,930,931,932,933,934],14,17,33,40,41,42,43,44,[465,936,925],{"__ignoreMap":567},[437,938,939,940,943,944,947,948,951,952,954,955,958,959,961,962,964,965,967,968,970],{},"The ",[465,941,942],{},"valueUpdater"," function updates a Vue ",[465,945,946],{},"ref"," object's value. It handles both direct assignments and transformations using a function. If ",[465,949,950],{},"updaterOrValue"," is a function, it's called with the current ",[465,953,946],{}," value, and the result is assigned to ",[465,956,957],{},"ref.value",". If it's not a function, it's directly assigned to ",[465,960,957],{},". This utility enhances flexibility in updating ",[465,963,946],{}," values. While Vue ",[465,966,946],{}," can manage reactive state directly, ",[465,969,942],{}," simplifies value updates, improving code readability and maintainability when the new state can be a direct value or a function generating it based on the current one.",[662,972,974],{"id":973},"make-header-cell-sortable","Make header cell sortable",[437,976,977,978,981],{},"We can now update the ",[465,979,980],{},"email"," header cell to add sorting controls.",[561,983,993],{"className":984,"code":985,"highlights":986,"language":598,"meta":599},[596],"// components/payments/columns.ts\nimport type {\n  ColumnDef,\n} from '@tanstack/vue-table'\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Button } from '@/components/ui/button'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n    {\n        accessorKey: '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",[987,988,989,853,990,927,991,992,928],5,10,11,13,15,16,[465,994,985],{"__ignoreMap":567},[437,996,997],{},"This will automatically sort the table (asc and desc) when the user toggles on the header cell.",[441,999,529],{"id":1000},"filtering",[437,1002,1003],{},"Let's add a search input to filter emails in our table.",[659,1005,1006,1011,1028],{},[662,1007,843,1009],{"id":1008},"update-datatable-2",[465,1010,846],{},[561,1012,1026],{"className":1013,"code":1014,"highlights":1015,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n} from '@tanstack/vue-table'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nimport {\n    Table,\n    TableBody,\n    TableCell,\n    TableHead,\n    TableHeader,\n    TableRow,\n} from \"@/components/ui/table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    getFilteredRowModel: getFilteredRowModel(),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n        \u003C/div>\n        \u003Cdiv class=\"border rounded-md\">\n            \u003CTable>{ ... }\u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\n\n",[852,989,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025],19,39,48,49,52,60,61,62,63,64,[465,1027,1014],{"__ignoreMap":567},[437,1029,1030,1031,1033,1034,1039],{},"Filtering is now enabled for the ",[465,1032,980],{}," column. You can add filters to other columns as well. See the ",[451,1035,1038],{"href":1036,"rel":1037},"https://tanstack.com/table/v8/docs/guide/filters",[455],"filtering docs"," for more information on customizing filters.",[441,1041,535],{"id":1042},"visibility",[437,1044,1045,1046,1049],{},"Adding column visibility is fairly simple using ",[465,1047,1048],{},"@tanstack/vue-table"," visibility API.",[659,1051,1052,1057,1084],{},[662,1053,843,1055],{"id":1054},"update-datatable-3",[465,1056,846],{},[561,1058,1082],{"className":1059,"code":1060,"highlights":1061,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n  VisibilityState,\n} from '@tanstack/vue-table'\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nimport {\n    Table,\n    TableBody,\n    TableCell,\n    TableHead,\n    TableHeader,\n    TableRow,\n} from \"@/components/ui/table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n            \u003CDropdownMenu>\n                \u003CDropdownMenuTrigger as-child>\n                    \u003CButton variant=\"outline\" class=\"ml-auto\">\n                        Columns\n                        \u003CChevronDown class=\"w-4 h-4 ml-2\" />\n                    \u003C/Button>\n                \u003C/DropdownMenuTrigger>\n                \u003CDropdownMenuContent align=\"end\">\n                    \u003CDropdownMenuCheckboxItem\n                        v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\" :key=\"column.id\"\n                        class=\"capitalize\" :modelValue=\"column.getIsVisible()\" @update:modelValue=\"(value) => {\n                            column.toggleVisibility(!!value)\n                        }\">\n                        {{ column.id }}\n                    \u003C/DropdownMenuCheckboxItem>\n                \u003C/DropdownMenuContent>\n            \u003C/DropdownMenu>\n        \u003C/div>\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\" :render=\"header.column.columnDef.header\"\n                                :props=\"header.getContext()\" />\n                        \u003C/TableHead>\n                    \u003C/TableRow>\n                \u003C/TableHeader>\n                \u003CTableBody>\n                    \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n                        \u003CTableRow v-for=\"row in table.getRowModel().rows\" :key=\"row.id\"\n                            :data-state=\"row.getIsSelected() ? 'selected' : undefined\">\n                            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n                                \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                    \u003Ctemplate v-else>\n                        \u003CTableRow>\n                            \u003CTableCell :colSpan=\"columns.length\" class=\"h-24 text-center\">\n                                No results.\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                \u003C/TableBody>\n            \u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\n\n",[1062,1063,988,989,853,990,927,1018,1064,1024,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081],6,9,59,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,[465,1083,1060],{"__ignoreMap":567},[437,1085,1086],{},"This adds a dropdown menu that you can use to toggle column visibility.",[441,1088,541],{"id":1089},"row-selection",[437,1091,1092],{},"Next, we're going to add row selection to our table.",[659,1094,1095,1099,1111,1116,1125,1128,1132,1139],{},[662,1096,1098],{"id":1097},"update-column-definitions","Update column definitions",[561,1100,1109],{"className":1101,"code":1102,"highlights":1103,"language":598,"meta":599},[596],"import type { ColumnDef } from '@tanstack/vue-table'\n\nimport { Checkbox } from '@/components/ui/checkbox'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n    {\n        id: 'select',\n        header: ({ table }) => h(Checkbox, {\n            'modelValue': table.getIsAllPageRowsSelected(),\n            'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),\n            'ariaLabel': 'Select all',\n        }),\n        cell: ({ row }) => h(Checkbox, {\n            'modelValue': row.getIsSelected(),\n            'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),\n            'ariaLabel': 'Select row',\n        }),\n        enableSorting: false,\n        enableHiding: false,\n    },\n]\n",[1104,1062,1105,1106,1063,988,989,853,990,927,991,992,928,1107,1016,1108],3,7,8,18,20,[465,1110,1102],{"__ignoreMap":567},[662,1112,843,1114],{"id":1113},"update-datatable-4",[465,1115,846],{},[561,1117,1123],{"className":1118,"code":1119,"highlights":1120,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\nconst rowSelection = ref({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n        get rowSelection() { return rowSelection.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n        \u003CTable />\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n",[988,1121,1122],22,27,[465,1124,1119],{"__ignoreMap":567},[437,1126,1127],{},"This adds a checkbox to each row and a checkbox in the header to select all rows.",[662,1129,1131],{"id":1130},"show-selected-rows","Show selected rows",[437,1133,1134,1135,1138],{},"You can show the number of selected rows using the ",[465,1136,1137],{},"table.getFilteredSelectedRowModel()"," API.",[561,1140,1144],{"className":1141,"code":1142,"highlights":1143,"language":704,"meta":599},[702],"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n        \u003CTable />\n    \u003C/div>\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/div>\n      \u003Cdiv class=\"space-x-2\">\n        \u003CPaginationButtons />\n      \u003C/div>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n",[1106,1063,988,989],[465,1145,1142],{"__ignoreMap":567},[659,1147,1148,1152,1155,1160,1183,1189,1199,1203,1206],{},[441,1149,1151],{"id":1150},"expanding","Expanding",[437,1153,1154],{},"Let's make rows expandable.",[662,1156,843,1158],{"id":1157},"update-datatable-5",[465,1159,846],{},[561,1161,1181],{"className":1162,"code":1163,"highlights":1164,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n  VisibilityState,\n  ExpandedState,\n} from '@tanstack/vue-table'\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    getExpandedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\nconst rowSelection = ref({})\nconst expanded = ref\u003CExpandedState>({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getExpandedRowModel: getExpandedRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),\n    onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n        get rowSelection() { return rowSelection.value },\n        get expanded() { return expanded.value },\n    },\n})\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n            \u003CDropdownMenu>\n                \u003CDropdownMenuTrigger as-child>\n                    \u003CButton variant=\"outline\" class=\"ml-auto\">\n                        Columns\n                        \u003CChevronDown class=\"w-4 h-4 ml-2\" />\n                    \u003C/Button>\n                \u003C/DropdownMenuTrigger>\n                \u003CDropdownMenuContent align=\"end\">\n                    \u003CDropdownMenuCheckboxItem\n                        v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\" :key=\"column.id\"\n                        class=\"capitalize\" :modelValue=\"column.getIsVisible()\" @update:modelValue=\"(value) => {\n                            column.toggleVisibility(!!value)\n                        }\">\n                        {{ column.id }}\n                    \u003C/DropdownMenuCheckboxItem>\n                \u003C/DropdownMenuContent>\n            \u003C/DropdownMenu>\n        \u003C/div>\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\" :render=\"header.column.columnDef.header\"\n                                :props=\"header.getContext()\" />\n                        \u003C/TableHead>\n                    \u003C/TableRow>\n                \u003C/TableHeader>\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' : undefined\">\n                            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n                                \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                        \u003CTableRow v-if=\"row.getIsExpanded()\">\n                          \u003CTableCell :colspan=\"row.getAllCells().length\">\n                            {{ JSON.stringify(row.original) }}\n                          \u003C/TableCell>\n                        \u003C/TableRow>\n                      \u003C/template>\n                    \u003C/template>\n                    \u003Ctemplate v-else>\n                        \u003CTableRow>\n                            \u003CTableCell :colSpan=\"columns.length\" class=\"h-24 text-center\">\n                                No results.\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                \u003C/TableBody>\n            \u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\n",[1105,1165,933,1020,1166,1024,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180],30,57,103,104,105,106,107,108,109,110,111,112,113,114,115,116,[465,1182,1163],{"__ignoreMap":567},[662,1184,1186,1187,692],{"id":1185},"add-the-expand-action-to-the-datatabledropdownvue-component","Add the expand action to the ",[465,1188,790],{},[561,1190,1197],{"className":1191,"code":1192,"highlights":1193,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\">\nimport { MoreHorizontal } from 'lucide-vue-next'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'\nimport { Button } from '@/components/ui/button'\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/script>\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/span>\n        \u003CMoreHorizontal class=\"w-4 h-4\" />\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem @click=\"$emit('expand')\">\n        Expand\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuSeparator />\n      \u003CDropdownMenuItem>View customer\u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C/DropdownMenuItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n",[853,990,927,1194,1195,1196],34,35,36,[465,1198,1192],{"__ignoreMap":567},[662,1200,1202],{"id":1201},"make-rows-expandable","Make rows expandable",[437,1204,1205],{},"Now we can update the action cell to add the expand control.",[561,1207,1211],{"className":1208,"code":1209,"highlights":1210,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\">\nexport const columns: ColumnDef\u003CPayment>[] = [\n  {\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\u003C/script>\n\n",[989],[465,1212,1209],{"__ignoreMap":567},[441,1214,547],{"id":1215},"reusable-components",[437,1217,1218,1219,1223],{},"Here are some components you can use to build your data tables. This is from the ",[451,1220,1222],{"href":1221},"/examples/tasks","Tasks"," demo.",[662,1225,1227],{"id":1226},"column-header","Column header",[437,1229,1230],{},"Make any column header sortable and hideable.",[561,1232,1235],{"className":1233,"code":1234,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\">\nimport type { Column } from '@tanstack/vue-table'\nimport { type Task } from '../data/schema'\nimport ArrowDownIcon from '~icons/radix-icons/arrow-down'\nimport ArrowUpIcon from '~icons/radix-icons/arrow-up'\nimport CaretSortIcon from '~icons/radix-icons/caret-sort'\nimport EyeNoneIcon from '~icons/radix-icons/eye-none'\n\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\ninterface DataTableColumnHeaderProps {\n  column: Column\u003CTask, any>\n  title: string\n}\n\ndefineProps\u003CDataTableColumnHeaderProps>()\n\u003C/script>\n\n\u003Cscript lang=\"ts\">\nexport default {\n  inheritAttrs: false,\n}\n\u003C/script>\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/span>\n          \u003CArrowDownIcon v-if=\"column.getIsSorted() === 'desc'\" class=\"w-4 h-4 ml-2\" />\n          \u003CArrowUpIcon v-else-if=\" column.getIsSorted() === 'asc'\" class=\"w-4 h-4 ml-2\" />\n          \u003CCaretSortIcon v-else class=\"w-4 h-4 ml-2\" />\n        \u003C/Button>\n      \u003C/DropdownMenuTrigger>\n      \u003CDropdownMenuContent align=\"start\">\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(false)\">\n          \u003CArrowUpIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Asc\n        \u003C/DropdownMenuItem>\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(true)\">\n          \u003CArrowDownIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Desc\n        \u003C/DropdownMenuItem>\n        \u003CDropdownMenuSeparator />\n        \u003CDropdownMenuItem @click=\"column.toggleVisibility(false)\">\n          \u003CEyeNoneIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Hide\n        \u003C/DropdownMenuItem>\n      \u003C/DropdownMenuContent>\n    \u003C/DropdownMenu>\n  \u003C/div>\n\n  \u003Cdiv v-else :class=\"$attrs.class\">\n    {{ title }}\n  \u003C/div>\n\u003C/template>\n\n",[465,1236,1234],{"__ignoreMap":567},[561,1238,1241],{"className":1239,"code":1240,"language":598,"meta":599},[596],"export const columns = [\n  {\n    accessorKey: \"email\",\n    header: ({ column }) => (\n        h(DataTableColumnHeader, {\n            column: column,\n            title: 'Email'\n        })\n    ),\n  },\n]\n",[465,1242,1240],{"__ignoreMap":567},[662,1244,246],{"id":1245},"pagination-1",[437,1247,1248],{},"Add pagination controls to your table including page size and selection count.",[561,1250,1253],{"className":1251,"code":1252,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\">\nimport { type Table } from '@tanstack/vue-table'\nimport { type Task } from '../data/schema'\nimport ChevronLeftIcon from '~icons/radix-icons/chevron-left'\nimport ChevronRightIcon from '~icons/radix-icons/chevron-right'\nimport DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'\nimport DoubleArrowRightIcon from '~icons/radix-icons/double-arrow-right'\n\nimport { Button } from '@/components/ui/button'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select'\n\ninterface DataTablePaginationProps {\n  table: Table\u003CTask>\n}\ndefineProps\u003CDataTablePaginationProps>()\n\u003C/script>\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/div>\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/p>\n        \u003CSelect\n          :model-value=\"`${table.getState().pagination.pageSize}`\"\n          @update:model-value=\"table.setPageSize\"\n        >\n          \u003CSelectTrigger class=\"h-8 w-[70px]\">\n            \u003CSelectValue :placeholder=\"`${table.getState().pagination.pageSize}`\" />\n          \u003C/SelectTrigger>\n          \u003CSelectContent side=\"top\">\n            \u003CSelectItem v-for=\"pageSize in [10, 20, 30, 40, 50]\" :key=\"pageSize\" :value=\"`${pageSize}`\">\n              {{ pageSize }}\n            \u003C/SelectItem>\n          \u003C/SelectContent>\n        \u003C/Select>\n      \u003C/div>\n      \u003Cdiv class=\"flex w-[100px] items-center justify-center text-sm font-medium\">\n        Page {{ table.getState().pagination.pageIndex + 1 }} of\n        {{ table.getPageCount() }}\n      \u003C/div>\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/span>\n          \u003CDoubleArrowLeftIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CChevronLeftIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CChevronRightIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CDoubleArrowRightIcon class=\"w-4 h-4\" />\n        \u003C/Button>\n      \u003C/div>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n",[465,1254,1252],{"__ignoreMap":567},[561,1256,1259],{"className":1257,"code":1258,"language":704,"meta":567},[702],"\u003CDataTablePagination :table=\"table\" />\n",[465,1260,1258],{"__ignoreMap":567},[662,1262,1264],{"id":1263},"column-toggle","Column toggle",[437,1266,1267],{},"A component to toggle column visibility.",[561,1269,1272],{"className":1270,"code":1271,"language":704,"meta":599},[702],"\u003Cscript setup lang=\"ts\">\nimport type { Table } from '@tanstack/vue-table'\nimport { computed } from 'vue'\nimport { type Task } from '../data/schema'\nimport MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'\n\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\ninterface DataTableViewOptionsProps {\n  table: Table\u003CTask>\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/script>\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\" />\n        View\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\" class=\"w-[150px]\">\n      \u003CDropdownMenuLabel>Toggle columns\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuSeparator />\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/DropdownMenuCheckboxItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n",[465,1273,1271],{"__ignoreMap":567},{"title":567,"searchDepth":572,"depth":572,"links":1275},[1276,1277,1278,1279,1280,1281,1287,1290,1291,1292,1293,1294,1295,1296],{"id":443,"depth":572,"text":14},{"id":483,"depth":572,"text":484},{"id":550,"depth":572,"text":20},{"id":588,"depth":572,"text":589},{"id":604,"depth":572,"text":605},{"id":654,"depth":572,"text":506,"children":1282},[1283,1284,1286],{"id":664,"depth":1104,"text":665},{"id":689,"depth":1104,"text":1285},"\u003CDataTable /> component",{"id":729,"depth":1104,"text":730},{"id":742,"depth":572,"text":743,"children":1288},[1289],{"id":751,"depth":1104,"text":752},{"id":774,"depth":572,"text":512},{"id":834,"depth":572,"text":246},{"id":898,"depth":572,"text":523},{"id":1000,"depth":572,"text":529},{"id":1042,"depth":572,"text":535},{"id":1089,"depth":572,"text":541},{"id":1215,"depth":572,"text":547,"children":1297},[1298,1299,1300],{"id":1226,"depth":1104,"text":1227},{"id":1245,"depth":1104,"text":246},{"id":1263,"depth":1104,"text":1264},"Powerful table and datagrids built using TanStack Table.","md",null,{"component":132},"---\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\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://tanstack.com/table/v8/docs/introduction#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 />` 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://tanstack.com/table) and the `\u003CTable />` component to build your own custom data table. We'll cover the following topics:\n\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- [Reusable Components](#reusable-components)\n\n\n## Installation\n\n1. Add the `\u003CTable />` component to your project:\n\n```bash\nnpx shadcn-vue@latest add table\n```\n\n2. Add `tanstack/vue-table` dependency:\n\n```bash\nnpm install @tanstack/vue-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  // ...\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          ├── 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- `data-table.vue` It will contain our `\u003CDataTable />` component.\n- `data-table-dropdown.vue` It will contain our `\u003CDropdownAction />` component.\n- `app.vue` This is where we'll fetch data and render our table.\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.\n\n```ts showLineNumbers\nimport { h } from 'vue'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n  {\n    accessorKey: 'amount',\n    header: () => h('div', { class: 'text-right' }, 'Amount'),\n    cell: ({ row }) => {\n      const amount = Number.parseFloat(row.getValue('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```\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 />` component\n\nNext, we'll create a `\u003CDataTable />` component to render our table.\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type { ColumnDef } from '@tanstack/vue-table'\nimport {\n  FlexRender,\n  getCoreRowModel,\n  useVueTable,\n} from '@tanstack/vue-table'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CTData, TValue>[]\n  data: TData[]\n}>()\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n})\n\u003C/script>\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\n              v-if=\"!header.isPlaceholder\" :render=\"header.column.columnDef.header\"\n              :props=\"header.getContext()\"\n            />\n          \u003C/TableHead>\n        \u003C/TableRow>\n      \u003C/TableHeader>\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' : undefined\"\n          >\n            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n              \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n            \u003C/TableCell>\n          \u003C/TableRow>\n        \u003C/template>\n        \u003Ctemplate v-else>\n          \u003CTableRow>\n            \u003CTableCell :colspan=\"columns.length\" class=\"h-24 text-center\">\n              No results.\n            \u003C/TableCell>\n          \u003C/TableRow>\n        \u003C/template>\n      \u003C/TableBody>\n    \u003C/Table>\n  \u003C/div>\n\u003C/template>\n```\n\n::callout\n\n**Tip**: If you find yourself using `\u003CDataTable />` in multiple places, this is the component you could make reusable by extracting it to `components/ui/data-table.vue`.\n\n`\u003CDataTable :columns=\"columns\" :data=\"data\" />`\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 './components/columns'\nimport { onMounted, ref } from 'vue'\nimport { columns } from './components/columns'\nimport DataTable from './components/DataTable.vue'\n\nconst data = ref\u003CPayment[]>([])\n\nasync function getData(): Promise\u003CPayment[]> {\n  // 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    // ...\n  ]\n}\n\nonMounted(async () => {\n  data.value = await getData()\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"container py-10 mx-auto\">\n    \u003CDataTable :columns=\"columns\" :data=\"data\" />\n  \u003C/div>\n\u003C/template>\n```\n\n\u003C/Steps>\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\nimport { h } from 'vue'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n  {\n    accessorKey: 'amount',\n    header: () => h('div', { class: 'text-right' }, 'Amount'),\n    cell: ({ row }) => {\n      const amount = Number.parseFloat(row.getValue('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/Steps>\n\n## Row Actions\n\nLet's add row actions to our table. We'll use a `\u003CDropdown />` 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-vue-next'\nimport { Button } from '@/components/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'\n\ndefineProps\u003C{\n  payment: {\n    id: string\n  }\n}>()\n\nfunction copy(id: string) {\n  navigator.clipboard.writeText(id)\n}\n\u003C/script>\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/span>\n        \u003CMoreHorizontal class=\"w-4 h-4\" />\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuSeparator />\n      \u003CDropdownMenuItem>View customer\u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C/DropdownMenuItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n```\n\n### Update columns definition\n\nUpdate our columns definition to add a new `actions` column. The `actions` cell returns a `\u003CDropdown />` component.\n\n```ts\nimport { ColumnDef } from '@tanstack/vue-table'\nimport DropdownAction from '@/components/DataTableDropDown.vue'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n  // ...\n  {\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/Steps>\n\n## Pagination\n\nNext, we'll add pagination to our table.\n\n\u003CSteps>\n\n### Update `\u003CDataTable>`\n\n```ts showLineNumbers {4,12}\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n})\n```\n\nThis will automatically paginate your rows into pages of 10. See the [pagination docs](https://tanstack.com/table/v8/docs/api/features/pagination) 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 />` component and the `table.previousPage()`, `table.nextPage()` API methods.\n\n```vue\n\u003Cscript lang=\"ts\" generic=\"TData, TValue\">\nimport { Button } from '@/components/ui/button'\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n  getPaginationRowModel: getPaginationRowModel(),\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>\n        { // .... }\n      \u003C/Table>\n    \u003C/div>\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/Button>\n      \u003CButton\n        variant=\"outline\"\n        size=\"sm\"\n        :disabled=\"!table.getCanNextPage()\"\n        @click=\"table.nextPage()\"\n      >\n        Next\n      \u003C/Button>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n```\n\nSee [Reusable Components](#reusable-components) section for a more advanced pagination component.\n\n\u003C/Steps>\n\n## Sorting\n\nLet's make the email column sortable.\n\n\u003CSteps>\n\n### Add the following into your `utils` file\n\n```ts\nimport type { Updater } from '@tanstack/vue-table'\nimport type { ClassValue } from 'clsx'\n\nimport type { Ref } from 'vue'\nimport { clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n```\n\n### Update `\u003CDataTable>`\n\n```vue showLineNumbers {4,14,17,33,40-44}\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  SortingState,\n} from '@tanstack/vue-table'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { h, ref } from 'vue'\n\nimport {\n  FlexRender,\n  getCoreRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useVueTable,\n} from '@tanstack/vue-table'\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\n\nconst props = defineProps\u003C{\n  columns: ColumnDef\u003CTData, TValue>[]\n  data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\n\nconst table = useVueTable({\n  get data() { return props.data },\n  get columns() { return props.columns },\n  getCoreRowModel: getCoreRowModel(),\n  getPaginationRowModel: getPaginationRowModel(),\n  getSortedRowModel: getSortedRowModel(),\n  onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n  state: {\n    get sorting() { return sorting.value },\n  },\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n      \u003CTable>{ ... }\u003C/Table>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n```\n\nThe `valueUpdater` function updates a Vue `ref` object's value. It handles both direct assignments and transformations using a function. If `updaterOrValue` is a function, it's called with the current `ref` value, and the result is assigned to `ref.value`. If it's not a function, it's directly assigned to `ref.value`. This utility enhances flexibility in updating `ref` values. While Vue `ref` can manage reactive state directly, `valueUpdater` simplifies value updates, improving code readability and maintainability when the new state can be a direct value or a function generating it based on the current one.\n\n### Make header cell sortable\n\nWe can now update the `email` header cell to add sorting controls.\n\n```ts showLineNumbers {5,10-17}\n// components/payments/columns.ts\nimport type {\n  ColumnDef,\n} from '@tanstack/vue-table'\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Button } from '@/components/ui/button'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n    {\n        accessorKey: '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.\n\n\u003C/Steps>\n\n## Filtering\n\nLet's add a search input to filter emails in our table.\n\n\u003CSteps>\n\n### Update `\u003CDataTable>`\n\n```vue showLineNumbers {4,11,19,39,48-49,52,60-64}\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n} from '@tanstack/vue-table'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nimport {\n    Table,\n    TableBody,\n    TableCell,\n    TableHead,\n    TableHeader,\n    TableRow,\n} from \"@/components/ui/table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    getFilteredRowModel: getFilteredRowModel(),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n        \u003C/div>\n        \u003Cdiv class=\"border rounded-md\">\n            \u003CTable>{ ... }\u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\n\n```\n\nFiltering is now enabled for the `email` column. You can add filters to other columns as well. See the [filtering docs](https://tanstack.com/table/v8/docs/guide/filters) for more information on customizing filters.\n\n\u003C/Steps>\n\n## Visibility\n\nAdding column visibility is fairly simple using `@tanstack/vue-table` visibility API.\n\n\u003CSteps>\n\n### Update `\u003CDataTable>`\n\n```vue showLineNumbers {6,9-14,48,59,63,75-91}\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n  VisibilityState,\n} from '@tanstack/vue-table'\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nimport {\n    Table,\n    TableBody,\n    TableCell,\n    TableHead,\n    TableHeader,\n    TableRow,\n} from \"@/components/ui/table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n            \u003CDropdownMenu>\n                \u003CDropdownMenuTrigger as-child>\n                    \u003CButton variant=\"outline\" class=\"ml-auto\">\n                        Columns\n                        \u003CChevronDown class=\"w-4 h-4 ml-2\" />\n                    \u003C/Button>\n                \u003C/DropdownMenuTrigger>\n                \u003CDropdownMenuContent align=\"end\">\n                    \u003CDropdownMenuCheckboxItem\n                        v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\" :key=\"column.id\"\n                        class=\"capitalize\" :modelValue=\"column.getIsVisible()\" @update:modelValue=\"(value) => {\n                            column.toggleVisibility(!!value)\n                        }\">\n                        {{ column.id }}\n                    \u003C/DropdownMenuCheckboxItem>\n                \u003C/DropdownMenuContent>\n            \u003C/DropdownMenu>\n        \u003C/div>\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\" :render=\"header.column.columnDef.header\"\n                                :props=\"header.getContext()\" />\n                        \u003C/TableHead>\n                    \u003C/TableRow>\n                \u003C/TableHeader>\n                \u003CTableBody>\n                    \u003Ctemplate v-if=\"table.getRowModel().rows?.length\">\n                        \u003CTableRow v-for=\"row in table.getRowModel().rows\" :key=\"row.id\"\n                            :data-state=\"row.getIsSelected() ? 'selected' : undefined\">\n                            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n                                \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                    \u003Ctemplate v-else>\n                        \u003CTableRow>\n                            \u003CTableCell :colSpan=\"columns.length\" class=\"h-24 text-center\">\n                                No results.\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                \u003C/TableBody>\n            \u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\n\n```\n\nThis adds a dropdown menu that you can use to toggle column visibility.\n\n\u003C/Steps>\n\n## Row Selection\n\nNext, we're going to add row selection to our table.\n\n\u003CSteps>\n\n### Update column definitions\n\n```ts showLineNumbers {3,6-20}\nimport type { ColumnDef } from '@tanstack/vue-table'\n\nimport { Checkbox } from '@/components/ui/checkbox'\n\nexport const columns: ColumnDef\u003CPayment>[] = [\n    {\n        id: 'select',\n        header: ({ table }) => h(Checkbox, {\n            'modelValue': table.getIsAllPageRowsSelected(),\n            'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),\n            'ariaLabel': 'Select all',\n        }),\n        cell: ({ row }) => h(Checkbox, {\n            'modelValue': row.getIsSelected(),\n            'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),\n            'ariaLabel': 'Select row',\n        }),\n        enableSorting: false,\n        enableHiding: false,\n    },\n]\n```\n\n### Update `\u003CDataTable>`\n\n```vue showLineNumbers {10,22,27}\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\nconst rowSelection = ref({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n        get rowSelection() { return rowSelection.value },\n    },\n})\n\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"border rounded-md\">\n        \u003CTable />\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n```\n\nThis adds a checkbox to each row and a checkbox in the header to select all rows.\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 />\n    \u003C/div>\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/div>\n      \u003Cdiv class=\"space-x-2\">\n        \u003CPaginationButtons />\n      \u003C/div>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n```\n\n\u003C/Steps>\n\n\u003CSteps>\n\n## Expanding\n\nLet's make rows expandable.\n\n### Update `\u003CDataTable>`\n\n```vue showLineNumbers {7,30,43,52,57,63,103-116}\n\u003Cscript setup lang=\"ts\" generic=\"TData, TValue\">\nimport type {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n  VisibilityState,\n  ExpandedState,\n} from '@tanstack/vue-table'\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\nimport { valueUpdater } from '@/components/ui/table/utils'\n\nimport { ArrowUpDown, ChevronDown } from 'lucide-vue-next'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { h, ref } from 'vue'\n\nimport {\n    FlexRender,\n    getCoreRowModel,\n    getPaginationRowModel,\n    getFilteredRowModel,\n    getSortedRowModel,\n    getExpandedRowModel,\n    useVueTable,\n} from \"@tanstack/vue-table\"\n\nconst props = defineProps\u003C{\n    columns: ColumnDef\u003CTData, TValue>[]\n    data: TData[]\n}>()\n\nconst sorting = ref\u003CSortingState>([])\nconst columnFilters = ref\u003CColumnFiltersState>([])\nconst columnVisibility = ref\u003CVisibilityState>({})\nconst rowSelection = ref({})\nconst expanded = ref\u003CExpandedState>({})\n\nconst table = useVueTable({\n    get data() { return props.data },\n    get columns() { return props.columns },\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getExpandedRowModel: getExpandedRowModel(),\n    onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),\n    onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),\n    onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),\n    onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),\n    onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),\n    state: {\n        get sorting() { return sorting.value },\n        get columnFilters() { return columnFilters.value },\n        get columnVisibility() { return columnVisibility.value },\n        get rowSelection() { return rowSelection.value },\n        get expanded() { return expanded.value },\n    },\n})\n\u003C/script>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cdiv class=\"flex items-center py-4\">\n            \u003CInput class=\"max-w-sm\" placeholder=\"Filter emails...\"\n                :model-value=\"table.getColumn('email')?.getFilterValue() as string\"\n                @update:model-value=\" table.getColumn('email')?.setFilterValue($event)\" />\n            \u003CDropdownMenu>\n                \u003CDropdownMenuTrigger as-child>\n                    \u003CButton variant=\"outline\" class=\"ml-auto\">\n                        Columns\n                        \u003CChevronDown class=\"w-4 h-4 ml-2\" />\n                    \u003C/Button>\n                \u003C/DropdownMenuTrigger>\n                \u003CDropdownMenuContent align=\"end\">\n                    \u003CDropdownMenuCheckboxItem\n                        v-for=\"column in table.getAllColumns().filter((column) => column.getCanHide())\" :key=\"column.id\"\n                        class=\"capitalize\" :modelValue=\"column.getIsVisible()\" @update:modelValue=\"(value) => {\n                            column.toggleVisibility(!!value)\n                        }\">\n                        {{ column.id }}\n                    \u003C/DropdownMenuCheckboxItem>\n                \u003C/DropdownMenuContent>\n            \u003C/DropdownMenu>\n        \u003C/div>\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\" :render=\"header.column.columnDef.header\"\n                                :props=\"header.getContext()\" />\n                        \u003C/TableHead>\n                    \u003C/TableRow>\n                \u003C/TableHeader>\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' : undefined\">\n                            \u003CTableCell v-for=\"cell in row.getVisibleCells()\" :key=\"cell.id\">\n                                \u003CFlexRender :render=\"cell.column.columnDef.cell\" :props=\"cell.getContext()\" />\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                        \u003CTableRow v-if=\"row.getIsExpanded()\">\n                          \u003CTableCell :colspan=\"row.getAllCells().length\">\n                            {{ JSON.stringify(row.original) }}\n                          \u003C/TableCell>\n                        \u003C/TableRow>\n                      \u003C/template>\n                    \u003C/template>\n                    \u003Ctemplate v-else>\n                        \u003CTableRow>\n                            \u003CTableCell :colSpan=\"columns.length\" class=\"h-24 text-center\">\n                                No results.\n                            \u003C/TableCell>\n                        \u003C/TableRow>\n                    \u003C/template>\n                \u003C/TableBody>\n            \u003C/Table>\n        \u003C/div>\n    \u003C/div>\n\u003C/template>\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-vue-next'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'\nimport { Button } from '@/components/ui/button'\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/script>\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/span>\n        \u003CMoreHorizontal class=\"w-4 h-4\" />\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\">\n      \u003CDropdownMenuLabel>Actions\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuItem @click=\"copy(payment.id)\">\n        Copy payment ID\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem @click=\"$emit('expand')\">\n        Expand\n      \u003C/DropdownMenuItem>\n      \u003CDropdownMenuSeparator />\n      \u003CDropdownMenuItem>View customer\u003C/DropdownMenuItem>\n      \u003CDropdownMenuItem>View payment details\u003C/DropdownMenuItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n```\n\n### Make rows expandable\n\nNow we can update the action cell to add the expand control.\n\n```vue showLineNumbers {11}\n\u003Cscript setup lang=\"ts\">\nexport const columns: ColumnDef\u003CPayment>[] = [\n  {\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\u003C/script>\n\n```\n\n\u003C/Steps>\n\n## Reusable Components\n\nHere are some components you can use to build your data tables. This is from the [Tasks](/examples/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/vue-table'\nimport { type Task } from '../data/schema'\nimport ArrowDownIcon from '~icons/radix-icons/arrow-down'\nimport ArrowUpIcon from '~icons/radix-icons/arrow-up'\nimport CaretSortIcon from '~icons/radix-icons/caret-sort'\nimport EyeNoneIcon from '~icons/radix-icons/eye-none'\n\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\ninterface DataTableColumnHeaderProps {\n  column: Column\u003CTask, any>\n  title: string\n}\n\ndefineProps\u003CDataTableColumnHeaderProps>()\n\u003C/script>\n\n\u003Cscript lang=\"ts\">\nexport default {\n  inheritAttrs: false,\n}\n\u003C/script>\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/span>\n          \u003CArrowDownIcon v-if=\"column.getIsSorted() === 'desc'\" class=\"w-4 h-4 ml-2\" />\n          \u003CArrowUpIcon v-else-if=\" column.getIsSorted() === 'asc'\" class=\"w-4 h-4 ml-2\" />\n          \u003CCaretSortIcon v-else class=\"w-4 h-4 ml-2\" />\n        \u003C/Button>\n      \u003C/DropdownMenuTrigger>\n      \u003CDropdownMenuContent align=\"start\">\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(false)\">\n          \u003CArrowUpIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Asc\n        \u003C/DropdownMenuItem>\n        \u003CDropdownMenuItem @click=\"column.toggleSorting(true)\">\n          \u003CArrowDownIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Desc\n        \u003C/DropdownMenuItem>\n        \u003CDropdownMenuSeparator />\n        \u003CDropdownMenuItem @click=\"column.toggleVisibility(false)\">\n          \u003CEyeNoneIcon class=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n          Hide\n        \u003C/DropdownMenuItem>\n      \u003C/DropdownMenuContent>\n    \u003C/DropdownMenu>\n  \u003C/div>\n\n  \u003Cdiv v-else :class=\"$attrs.class\">\n    {{ title }}\n  \u003C/div>\n\u003C/template>\n\n```\n\n```ts showLineNumbers\nexport const columns = [\n  {\n    accessorKey: \"email\",\n    header: ({ column }) => (\n        h(DataTableColumnHeader, {\n            column: 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/vue-table'\nimport { type Task } from '../data/schema'\nimport ChevronLeftIcon from '~icons/radix-icons/chevron-left'\nimport ChevronRightIcon from '~icons/radix-icons/chevron-right'\nimport DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'\nimport DoubleArrowRightIcon from '~icons/radix-icons/double-arrow-right'\n\nimport { Button } from '@/components/ui/button'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select'\n\ninterface DataTablePaginationProps {\n  table: Table\u003CTask>\n}\ndefineProps\u003CDataTablePaginationProps>()\n\u003C/script>\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/div>\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/p>\n        \u003CSelect\n          :model-value=\"`${table.getState().pagination.pageSize}`\"\n          @update:model-value=\"table.setPageSize\"\n        >\n          \u003CSelectTrigger class=\"h-8 w-[70px]\">\n            \u003CSelectValue :placeholder=\"`${table.getState().pagination.pageSize}`\" />\n          \u003C/SelectTrigger>\n          \u003CSelectContent side=\"top\">\n            \u003CSelectItem v-for=\"pageSize in [10, 20, 30, 40, 50]\" :key=\"pageSize\" :value=\"`${pageSize}`\">\n              {{ pageSize }}\n            \u003C/SelectItem>\n          \u003C/SelectContent>\n        \u003C/Select>\n      \u003C/div>\n      \u003Cdiv class=\"flex w-[100px] items-center justify-center text-sm font-medium\">\n        Page {{ table.getState().pagination.pageIndex + 1 }} of\n        {{ table.getPageCount() }}\n      \u003C/div>\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/span>\n          \u003CDoubleArrowLeftIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CChevronLeftIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CChevronRightIcon class=\"w-4 h-4\" />\n        \u003C/Button>\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/span>\n          \u003CDoubleArrowRightIcon class=\"w-4 h-4\" />\n        \u003C/Button>\n      \u003C/div>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n```\n\n```vue\n\u003CDataTablePagination :table=\"table\" />\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/vue-table'\nimport { computed } from 'vue'\nimport { type Task } from '../data/schema'\nimport MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'\n\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\ninterface DataTableViewOptionsProps {\n  table: Table\u003CTask>\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/script>\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\" />\n        View\n      \u003C/Button>\n    \u003C/DropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"end\" class=\"w-[150px]\">\n      \u003CDropdownMenuLabel>Toggle columns\u003C/DropdownMenuLabel>\n      \u003CDropdownMenuSeparator />\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/DropdownMenuCheckboxItem>\n    \u003C/DropdownMenuContent>\n  \u003C/DropdownMenu>\n\u003C/template>\n```",{"title":170,"description":1301},"X81WqZ1lmoEEsiwfjfBy7L_UhG-83avxB98cdSzq4SY",[1309,1310],{"title":166,"path":167,"stem":168,"children":-1},{"title":174,"path":175,"stem":176,"children":-1},1775649985056]