Skip to content
Accessibility: Work in progress
Translations: Work in progress

Table

A flexible and powerful data table component that supports sorting, filtering, pagination, row selection, and customizable columns.

Basic Usage

Using cellFormatter we can render custom content inside the table cells.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="basic-table-wrapper" max-width="100%">
  <HLDataTable
    id="basic-table"
    ref="tableInstance"
    :columns="columns"
    :page-size="6"
    :data="data"
    :horizontal-borders="false"
    :vertical-borders="false"
    max-height="900px"
  />
</HLDataTableWrapper>
ts
import { HLDataTable, HLDataTableWrapper, HLProgress, HLSpace, HLIcon } from '@platform-ui/highrise'
import { Star01Icon } from '@gohighlevel/ghl-icons/24/outline'
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]
ts
const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    rating: 4,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    rating: 3,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    rating: 2,
    DOB: '2024-01-01',
  },
])

Table with Breadcrumb and Pagination

Pagination can also be used in the table header by passing the header slot to the HLDataTableWrapper component. The breadcrumb can be removed if not needed.

ID

First Name

Age

Progress

Rating

DOB

html
<div class="h-[40vh]">
  <HLDataTableWrapper id="data-table-wrapper-default" max-width="100%" :fillParentHeight="true">
    <template #footer>
      <div class="flex justify-between items-center">
        <HLBreadcrumb size="md" :breadcrumbs="breadcrumbs" />
        <HLPagination
          v-if="tableInstanceBreadcrumbPagination"
          id="searchable-table-pagination"
          :item-count="tableInstanceBreadcrumbPagination.table.getFilteredRowModel().rows.length"
          :per-page="50"
          :current-page="tableInstanceBreadcrumbPagination.table?.getState().pagination.pageIndex + 1"
          :pages-to-display="7"
          :per-page-dropdown-options="paginationOptions"
          size="sm"
          per-page-text="Rows per page"
          @update:per-page="updatePageSizeBreadcrumbPagination"
          @update:page="
              page => {
                tableInstanceBreadcrumbPagination.table?.setPageIndex(page - 1)
              }
            "
        >
          <template #prev> Previous </template>
          <template #next> Next </template>
        </HLPagination>
      </div>
    </template>
    <HLDataTable
      id="data-table-default"
      :columns="columns"
      :data="data"
      max-height="auto"
      :page-size="50"
      ref="tableInstanceBreadcrumbPagination"
    >
    </HLDataTable>
  </HLDataTableWrapper>
</div>
ts
const breadcrumbs = [
  {
    label: 'BC1',
    href: 'https://google.com',
  },
  {
    label: 'BC2',
    href: 'https://google.com',
  },
  {
    label: 'BC3',
    href: 'https://google.com',
  },
]
ts
const paginationOptions = [
  {
    key: 50,
    label: 50,
  },
  {
    key: 100,
    label: 100,
  },
  {
    key: 200,
    label: 200,
  },
  {
    key: 300,
    label: 300,
  },
]
const updatePageSize = value => {
  tableInstance.value.table?.setPageSize(value)
}

Responsive Columns

Make the table columns have same % of width, by setting the responsive-column-width prop to true.

First Name

Age

Rating

DOB

html
<HLDataTableWrapper id="basic-table-wrapper" max-width="800px" responsive-column-width>
  <HLDataTable
    id="responsive-table"
    ref="tableInstance"
    :columns="columnsResponsive"
    :data="data"
    :column-hover="true"
    :horizontal-borders="false"
    :vertical-borders="false"
    max-height="900px"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const columnsResponsive = [
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 30, // this will occupy 30% of the table width
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    size: 10, // this will occupy 10% of the table width
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    header: 'Rating',
    size: 30, // this will occupy 30% of the table width
    meta: {
      align: 'center',
      headerAlign: 'start',
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    size: 30, // this will occupy 30% of the table width
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]

Search and filter the table data by setting the show-global-search,show-header and prop to true. Also, handle the @update:global-filter event to filter the table data.

The search placeholder can be customized using the search-placeholder prop on the HLDataTableWrapper.

Search table data
html
<HLButton class="mb-2" @click="updateGlobalFilterForTableWithSearch('')">Clear Filter</HLButton>
<HLDataTableWrapper
  id="row-expand-table-wrapper"
  max-width="1000px"
  :show-header="true"
  :show-global-search="true"
  :global-filter="filterString"
  search-placeholder="Search table data"
  @update:global-filter="updateGlobalFilter"
>
  <HLDataTable ref="tableInstance" id="table-with-search" :columns="columns" :data="data" max-height="auto" :page-size="6"> </HLDataTable>
</HLDataTableWrapper>
ts
import { ref } from 'vue'

const filterString = ref('')
const tableInstance = ref<any>(null)
const updateGlobalFilter = globalFilter => {
  filterString.value = globalFilter
  tableInstance.value.table?.setGlobalFilter(globalFilter) // set global filter to the table in current rows or make API call to get the filtered data from the server
}

Column Hover

Highlight the entire column when hovering over it. This can be controlled by the column-hover prop.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
  <HLDataTable
    id="basic-table"
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :column-hover="true"
    :horizontal-borders="false"
    :vertical-borders="false"
    max-height="900px"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]

const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    rating: 4,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    rating: 3,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    rating: 2,
    DOB: '2024-01-01',
  },
])

Column Resize

Drag the column header to adjust the column width. This can be controlled by the column-resizing prop.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
  <HLDataTable id="basic-table" ref="tableInstance" :columns="columns" :data="data" :column-resizing="true" max-height="900px">
  </HLDataTable>
</HLDataTableWrapper>
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]

const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    rating: 4,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    rating: 3,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    rating: 2,
    DOB: '2024-01-01',
  },
])

Infinite Scroll

Load more data on scroll while listening to the @scroll event.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="infinite-scroll-table-wrapper" max-width="1000px">
  <HLDataTable
    ref="infiniteScrollInstance"
    :columns="columns"
    :data="infiniteScrollData"
    :loading="infiniteScrollLoading"
    row-unique-id="id"
    @scroll="handleScroll"
    max-height="500px"
  />
</HLDataTableWrapper>
ts
import { ref } from 'vue'
import { HLDataTableWrapper, HLDataTable } from '@platform-ui/highrise'

const infiniteScrollLoading = ref(false)
const infiniteScrollInstance = ref(null)
const infiniteScrollData = ref([]) // your initial data
const handleScroll = async event => {
  const scrollPosition = event.target.scrollTop + event.target.clientHeight
  if (scrollPosition >= event.target.scrollHeight - 10) {
    infiniteScrollLoading.value = true
    const result = await fetchMoreData() // your API call to fetch more data
    infiniteScrollData.value = [...infiniteScrollData.value, ...result]
    infiniteScrollInstance.value.table?.setPageSize(infiniteScrollData.value.length)
    infiniteScrollLoading.value = false
  }
}

const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    cellFormatter: ({ row }) => {
      return row.original.id
    },
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'start',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    filterFn: 'greaterThan',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'end',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 300,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, { default: () => stars })
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
      headerAlign: 'end',
    },
    size: 200,
    header: 'DOB',
  },
]

Column Re-Order

Reorder columns by dragging and dropping the column headers. This can be controlled by the column-reordering prop.

ID

First Name

Age

Progress

Rating

DOB

Vue
html
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
  <HLDataTable id="basic-table" ref="tableInstance" :columns="columns" :data="data" :column-reordering="true" max-height="900px">
  </HLDataTable>
</HLDataTableWrapper>

Sticky Columns

Make columns sticky to the the table. This can be controlled by the freezed-columns prop.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="sticky-column-table-wrapper" max-width="1000px">
  <HLDataTable
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :striped="true"
    :horizontal-borders="true"
    :vertical-borders="true"
    max-height="900px"
    :freezed-columns="{ left: ['id', 'firstName'] }"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]

const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    rating: 4,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    rating: 3,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    rating: 2,
    DOB: '2024-01-01',
  },
])

Column Sort

By clicking on the column header, you can sort the data in ascending or descending order.

Id

First Name

Age

Progress

Rating

DOB

html
<template>
  <HLDataTableWrapper id="data-table-wrapper" max-width="1000px">
    <HLDataTable
      id="data-table"
      ref="tableInstanceColumnSort"
      :columns="sortTableColumns"
      :data="data"
      :striped="true"
      :horizontal-borders="true"
      :vertical-borders="true"
      max-height="900px"
      row-height="40px"
      :page-size="6"
      header-row-height="40px"
      @update:column-clicked="handleColumnClickSort"
      @no-data="handleNoData"
      :row-hover="true"
      :column-hover="true"
    >
      <template #no-data>
        <HLEmpty
          id="empty-state"
          size="md"
          title="No data available to display. This is a placeholder slot"
          description="This is a placeholder"
          positive-text="Refresh"
          negative-text="Try again"
          icon="info"
        />
      </template>
    </HLDataTable>
  </HLDataTableWrapper>
</template>
ts
import { ref } from 'vue'
import { HLDataTableWrapper, HLDataTable, HLEmpty } from '@platform-ui/highrise'
import SimpleSort from './SimpleSort.vue'
import data from './data.ts'
const sortTableSortedColumn = ref(null)
const sortTableSortDir = ref(null)
const tableInstanceColumnSort = ref<any>(null)
const sortTableColumns = [
  {
    id: 'id',
    header: 'Id',
    accessorKey: 'id',
    size: 50,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'firstName',
    header: {
      text: 'First Name',
      icon: UserCircleIcon,
      filterComponent: h(SimpleSort, {
        sortDir: sortTableSortDir,
        sortedColumn: sortTableSortedColumn,
        columnName: 'firstName',
      }),
    },
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    cellFormatter: ({ row }) => {
      if (typeof row.original.firstName === 'string') {
        return row.original.firstName
      } else {
        return h(row.original.firstName)
      }
    },
    meta: {
      align: 'left',
    },
  },
  {
    id: 'age',
    header: {
      text: 'Age',
      icon: BarChartCircle01Icon,
      filterComponent: h(SimpleSort, {
        sortDir: sortTableSortDir,
        sortedColumn: sortTableSortedColumn,
        columnName: 'age',
      }),
    },
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: { text: 'Progress', icon: UserCircleIcon },
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'left',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    header: { text: 'Rating', icon: Star01Icon },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--yellow-500)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon))
      }
      return h(
        HLTooltip,
        { trigger: 'hover', variant: 'dark', placement: 'top-start' },
        {
          trigger: () => h('div', { class: 'flex items-center gap-2 cursor-pointer' }, stars),
          default: () =>
            h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, () => [
              h(HLIcon, { size: '20', color: 'white' }, InfoCircleIcon),
              h(HLText, { size: 'sm', weight: 'semibold' }, row.original.rating),
            ]),
        }
      )
    },
    meta: {
      headerAlign: 'left',
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
    header: {
      text: 'DOB',
      icon: CalendarIcon,
      filterComponent: h(SimpleSort, {
        sortDir: sortTableSortDir,
        sortedColumn: sortTableSortedColumn,
        columnName: 'DOB',
      }),
    },
  },
]

const handleColumnClickSort = columnId => {
  if (columnId === 'firstName' || columnId === 'DOB' || columnId === 'age') {
    if (tableInstanceColumnSort.value.table.getState()?.sorting[0]?.id === columnId) {
      const desc = tableInstanceColumnSort.value.table.getState()?.sorting[0]?.desc
      if (desc) {
        sortTableSortedColumn.value = ''
        sortTableSortDir.value = null
        tableInstanceColumnSort.value.table.setSorting([])
      } else {
        sortTableSortedColumn.value = columnId
        sortTableSortDir.value = 'desc'
        tableInstanceColumnSort.value.table.setSorting([{ id: columnId, desc: true }])
      }
    } else {
      sortTableSortedColumn.value = columnId
      sortTableSortDir.value = 'asc'
      tableInstanceColumnSort.value.table.setSorting([{ id: columnId, desc: false }])
    }
  }
}
function setSortTableSort(id, desc) {
  sortTableSortedColumn.value = id
  sortTableSortDir.value = desc ? 'desc' : 'asc'
  tableInstanceColumnSort.value.table.setSorting([{ id, desc }])
}
function resetSortTableSort() {
  sortTableSortedColumn.value = null
  sortTableSortDir.value = null
  tableInstanceColumnSort.value.table.setSorting([])
}
html
<script setup lang="ts">
  import { ArrowDownIcon } from '@gohighlevel/ghl-icons/24/outline'
  import { Ref } from 'vue'
  import { HLIcon } from '@platform-ui/highrise'

  interface Props {
    sortDir: Ref<'asc' | 'desc' | null>
    sortedColumn: Ref<string | null>
    columnName: string
  }
  const props = defineProps<Props>()
</script>
<template>
  <HLIcon
    v-if="sortedColumn.value === columnName"
    size="14"
    color="var(--primary-600)"
    :class="[sortDir.value === 'asc' ? 'rotate-180' : '']"
  >
    <ArrowDownIcon />
  </HLIcon>
</template>
ts
const data = [
  {
    id: 1,
    firstName: 'Felton',
    lastName: 'Ratke',
    age: 32,
    rating: 2,
    status: 'curatio',
    progress: 59,
    toggle: false,
    DOB: '2025/06/28',
  },
  {
    id: 2,
    firstName: 'Marlon',
    lastName: 'Graham',
    age: 54,
    rating: 2,
    status: 'adiuvo',
    progress: 32,
    toggle: false,
    DOB: '2024/05/01',
  },
  {
    id: 3,
    firstName: 'Woodrow',
    lastName: 'Cronin',
    age: 49,
    rating: 1,
    status: 'solio',
    progress: 8,
    toggle: false,
    DOB: '2025/05/31',
  },
  {
    id: 4,
    firstName: 'Abe',
    lastName: 'Padberg',
    age: 61,
    rating: 2,
    status: 'trans',
    progress: 41,
    toggle: false,
    DOB: '2024/10/26',
  },
  {
    id: 5,
    firstName: 'Stephany',
    lastName: 'Berge',
    age: 69,
    rating: 5,
    status: 'eius',
    progress: 30,
    toggle: true,
    DOB: '2026/02/18',
  },
]

Column Filter and Sort

NameFilter and AgeFilter are custom components used for filtering and sorting the data.

Id

First Name

Age

Progress

Rating

DOB

html
<template>
  <HLDataTableWrapper id="column-filter-table-wrapper">
    <HLDataTable ref="tableInstance" id="column-filter-table" :columns="columns" :data="data" />
  </HLDataTableWrapper>
</template>
ts
import { BarChartCircle01Icon, Star01Icon, UserCircleIcon } from '@gohighlevel/ghl-icons/24/outline'
import { DataTableColumn, HLDataTable, HLDataTableWrapper, HLIcon, HLProgress, HLSpace } from '@platform-ui/highrise'
import { h, ref } from 'vue'
import NameFilter from './NameFilter.vue'

const sortedColumn = ref<string | null>(null)
const sortDir = ref<'asc' | 'desc' | null>(null)
const tableInstance = ref<any>(null)
const data = ref([]) // TODO: replace with actual data
const columns: DataTableColumn[] = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: {
      text: 'First Name',
      icon: h(UserCircleIcon),
      filterComponent: h(NameFilter, {
        onSetFilter: setFilter,
        onClearFilter: clearFilter,
        onSetSort: setSort,
        sortedColumn,
        sortDir,
        id: 'first-name-filter',
        onResetSort: resetSort,
      }),
    },
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'start',
    },
  },
  {
    id: 'age',
    header: {
      text: 'Age',
      icon: h(BarChartCircle01Icon),
      filterComponent: h(AgeFilter, {
        onSetFilter: setFilter,
        onClearFilter: clearFilter,
        onSetSort: setSort,
        sortedColumn,
        sortDir,
        id: 'age-filter',
        onResetSort: resetSort,
      }),
    },
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'end',
      headerAlign: 'end',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }: any) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }: any) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'end',
      headerAlign: 'end',
    },
    header: 'DOB',
  },
]

function setSort(id: string, dir: 'asc' | 'desc') {
  sortedColumn.value = id
  sortDir.value = dir ? 'desc' : 'asc'
  tableInstance.value?.table.setSorting([{ id, dir }])
}
function resetSort() {
  sortedColumn.value = null
  sortDir.value = null
  tableInstance.value?.table.setSorting([])
}
function setFilter(id: string, filterFn: any, value: any) {
  const column = tableInstance.value?.table.getColumn(id)
  column.columnDef.filterFn = filterFn
  column.setFilterValue(value)
}
function clearFilter(id: string) {
  const column = tableInstance.value?.table.getColumn(id)
  column.setFilterValue(undefined)
}
html
<script setup lang="ts">
  import { CheckIcon, SearchSmIcon } from '@gohighlevel/ghl-icons/24/outline'
  import {
    HLAccordion,
    HLAccordionItem,
    HLButton,
    HLCheckbox,
    HLCheckboxGroup,
    HLIcon,
    HLInput,
    HLSelect,
    HLSpace,
    HLText,
  } from '@platform-ui/highrise'
  import { nextTick, Ref, ref } from 'vue'
  import FilterCompoent from './FilterComponent.vue'

  const emit = defineEmits(['setSort', 'setFilter', 'clearFilter', 'resetSort'])
  const filterIconColor = ref('var(--gray-400)')
  const nameInputValue = ref('')
  const isFilterApplied = ref(false)
  defineProps<{
    id: string
    sortedColumn: Ref<string | null>
    sortDir: Ref<'asc' | 'desc' | null>
  }>()
  const dropdownOptions = [
    {
      value: 'none',
      label: 'None',
      inputCount: 0,
    },
    {
      value: 'isEmpty',
      label: 'Is Empty',
      inputCount: 0,
    },
    {
      value: 'isNotEmpty',
      label: 'Is Not Empty',
      inputCount: 0,
    },
    {
      value: 'contains',
      label: 'Text contains',
      inputCount: 1,
    },
    {
      value: 'notContains',
      label: 'Text does not contain',
      inputCount: 1,
    },
    {
      value: 'startsWith',
      label: 'Text starts with',
      inputCount: 1,
    },
    {
      value: 'endsWith',
      label: 'Text ends with',
      inputCount: 1,
    },
    {
      value: 'equals',
      label: 'Text is exactly',
      inputCount: 1,
    },
  ]

  const selectedDropdownOption = ref(dropdownOptions[0])

  const handleSort = (dir: 'asc' | 'desc') => {
    emit('setSort', 'firstName', dir == 'desc')
  }
  function handleResetSort() {
    emit('resetSort', 'firstName')
  }
  function handleClear() {
    nameInputValue.value = ''
    isFilterApplied.value = false
    filterIconColor.value = 'var(--gray-400)'
    checkboxValue.value = []
    emit('clearFilter', 'firstName')
  }
  function handleApply() {
    if (checkboxValue.value.length) {
      emit('setFilter', 'firstName', 'containsInArray', checkboxValue.value)
      isFilterApplied.value = true
      filterIconColor.value = 'var(--primary-600)'
      return
    }
    if (selectedDropdownOption.value.value === 'none') {
      handleClear()
      return
    }
    isFilterApplied.value = true
    filterIconColor.value = 'var(--primary-600)'
    emit('setFilter', 'firstName', selectedDropdownOption.value.value, nameInputValue.value)
  }
  const checkboxOptions = [
    { label: '(Blank)', value: '' },
    { label: 'Mark', value: 'Mark' },
    { label: 'Jhon', value: 'Jhon' },
    { label: 'Abdul', value: 'Abdul' },
    { label: 'Victor', value: 'Victor' },
  ]
  const checkboxValue = ref<string[]>([])
  const checkboxInputValue = ref('')
  const nameInputValueRef = ref<any>(null)
  const updateSelectedDropdownOption = (key: string, option: any) => {
    selectedDropdownOption.value = option
    nextTick(() => {
      if (option.inputCount) {
        nameInputValueRef.value?.focus()
      }
    })
  }
</script>
<template>
  <FilterCompoent
    :id="`${id}-name-filter`"
    column-name="firstName"
    :sorted-column="sortedColumn"
    :is-filter-applied="isFilterApplied"
    :sort-dir="sortDir"
    @reset-sort="handleResetSort"
    @set-sort="handleSort"
    @clear-filter="handleClear"
    @apply-click="handleApply"
  >
    <div class="p-2 flex flex-col gap-2">
      <div
        class="data-table-filter-item flex"
        :sort-selected="
          sortedColumn.value == 'firstName' && sortDir.value == 'asc'
        "
        :class="{
          'sort-selected':
            sortedColumn.value == 'firstName' && sortDir.value == 'asc',
        }"
        @click="handleSort('asc')"
      >
        <HLText size="md" :weight="'medium'">Sort Ascending A-Z </HLText>
        <HLIcon v-if="sortedColumn.value == 'firstName' && sortDir.value == 'asc'" size="16" color="var(--primary-700)">
          <CheckIcon />
        </HLIcon>
      </div>
      <div
        class="data-table-filter-item flex"
        :class="{
          'sort-selected':
            sortedColumn.value == 'firstName' && sortDir.value == 'desc',
        }"
        @click="handleSort('desc')"
      >
        <HLText size="md" :weight="'medium'">Sort Descending Z-A </HLText>
        <HLIcon v-if="sortedColumn.value == 'firstName' && sortDir.value == 'desc'" size="16" color="var(--primary-700)">
          <CheckIcon />
        </HLIcon>
      </div>
      <HLAccordion size="sm" :border="false" :zero-padding="true">
        <HLAccordionItem id="1" title="Filter by Condition" name="1" size="sm" :hover-effect="false">
          <div class="flex flex-col gap-1 pt-1">
            <HLSelect
              id="select-default"
              size="xs"
              :options="dropdownOptions"
              :value="selectedDropdownOption.value"
              :option-height="26"
              @update:value="updateSelectedDropdownOption"
            ></HLSelect>
            <HLInput
              v-if="selectedDropdownOption.inputCount"
              id="name-filter"
              ref="nameInputValueRef"
              v-model:model-value="nameInputValue"
              placeholder="Filter by name"
              size="xs"
              clearable
              @update:value="nameInputValue = $event"
            />
          </div>
        </HLAccordionItem>
      </HLAccordion>

      <HLAccordion size="sm" :border="false" :zero-padding="true" :default-expanded-names="['1']">
        <HLAccordionItem id="1" title="Filter by Values" name="1" :hover-effect="false">
          <div class="flex flex-col gap-2 pt-2">
            <HLSpace :size="12" align="center" :wrap-item="false">
              <HLButton
                id="select-all-checkbox"
                size="3xs"
                variant="text"
                color="blue"
                @click="checkboxValue = checkboxOptions.map(i => i.value)"
              >
                Select All {{ checkboxOptions.length }}
              </HLButton>
              <HLButton id="select-all-checkbox" size="3xs" variant="text" color="gray" @click="checkboxValue = []"> Clear </HLButton>
              <HLText size="xs" :weight="'medium'" style="margin-left: auto">Displaying {{ checkboxOptions.length }}</HLText>
            </HLSpace>
            <HLInput
              id="name-filter"
              v-model:model-value="checkboxInputValue"
              :prefix-icon="SearchSmIcon as any"
              placeholder="Filter by placeholder"
              size="2xs"
              clearable
              @update:value="checkboxInputValue = $event"
            />
            <HLCheckboxGroup
              id="checkbox-group-1"
              :value="checkboxValue"
              size="xs"
              style="padding: 4px"
              @update:value="e => (checkboxValue = e as string[])"
            >
              <HLSpace :vertical="true">
                <HLCheckbox
                  v-for="(i, index) in checkboxOptions.filter(i =>
                    i.label.includes(checkboxInputValue)
                  )"
                  :id="`${id}-checkbox-${index}`"
                  :key="index"
                  :value="i.value"
                  size="xs"
                >
                  {{ i.label }}
                </HLCheckbox>
              </HLSpace>
            </HLCheckboxGroup>
          </div>
        </HLAccordionItem>
      </HLAccordion>
    </div>
  </FilterCompoent>
</template>
html
<script setup lang="ts">
  import { ArrowDownIcon, ArrowUpIcon, FilterLinesIcon } from '@gohighlevel/ghl-icons/24/outline'
  import { HLButton, HLIcon, HLPopover } from '@platform-ui/highrise'
  import { Ref, ref } from 'vue'

  const emit = defineEmits(['setSort', 'clearFilter', 'applyClick', 'resetSort'])
  const filterIconColor = ref('var(--gray-400)')
  const props = withDefaults(
    defineProps<{
      id: string
      sortedColumn: Ref<string | null>
      columnName: string
      isFilterApplied: boolean
      showSort?: boolean
      sortDir: Ref<'asc' | 'desc' | null>
    }>(),
    {
      showSort: true,
    }
  )

  function handleClear() {
    filterIconColor.value = 'var(--gray-400)'
    emit('clearFilter', props.columnName)
  }
  function handleApply() {
    filterIconColor.value = 'var(--primary-600)'
    emit('applyClick', props.columnName)
  }
  function handleShow(show: boolean) {
    if (show) {
      filterIconColor.value = 'var(--primary-600)'
    } else if (!props.isFilterApplied) {
      filterIconColor.value = 'var(--gray-400)'
    }
  }
  function toggleSort() {
    if (props.sortDir.value == 'asc') {
      emit('setSort', props.columnName, true)
    } else if (props.sortDir.value == 'desc') {
      emit('resetSort', props.columnName)
    }
  }
</script>
<template>
  <div>
    <HLIcon v-if="sortedColumn.value == columnName" size="14" color="var(--primary-600)" style="cursor: pointer" @click="toggleSort()">
      <ArrowUpIcon v-if="sortDir.value == 'asc'" />
      <ArrowDownIcon v-if="sortDir.value == 'desc'" />
    </HLIcon>
    <HLPopover :id="id" trigger="click" placement="bottom-end" :show-arrow="false" @show="handleShow">
      <template #trigger>
        <HLIcon size="14" :color="filterIconColor">
          <FilterLinesIcon />
        </HLIcon>
      </template>
      <template #default>
        <slot></slot>
      </template>
      <template #footer>
        <div class="flex gap-2 justify-end p-2">
          <HLButton :id="`${id}-filter-clear`" variant="ghost" color="gray" size="3xs" @click="handleClear"> Clear </HLButton>
          <HLButton :id="`${id}-filter-apply`" variant="ghost" color="blue" size="3xs" @click="handleApply"> Apply </HLButton>
        </div>
      </template>
    </HLPopover>
  </div>
</template>

Empty State

Display an empty state when the table has no data using the no-data slot. This will be displayed when the table data prop is an empty array.

ID

First Name

Progress

Rating

DOB

No data available to display. This is a placeholder slot

This is a placeholder

html
<HLDataTableWrapper>
  <HLDataTable
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :striped="true"
    :horizontal-borders="true"
    :vertical-borders="true"
    max-height="900px"
  >
    <template #no-data>
      <HLEmpty
        id="empty-state"
        size="md"
        title="No data available to display. This is a placeholder slot"
        description="This is a placeholder"
        positive-text="Refresh"
        negative-text="Try again"
      />
    </template>
  </HLDataTable>
</HLDataTableWrapper>
ts
import { HLDataTable, HLDataTableWrapper, HLEmpty } from '@platform-ui/highrise'

Error State

Display an error state when the table encounters an error using the no-data slot. This will be displayed when the table data prop is an empty array.

ID

First Name

Progress

Rating

DOB

Something went wrong while fetching your appointments

You can try again now or after 10 minutes

html
<HLDataTableWrapper id="error-state-table-wrapper" max-width="1000px">
  <HLDataTable
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :striped="true"
    :horizontal-borders="true"
    :vertical-borders="true"
    max-height="900px"
  >
    <template #no-data>
      <HLEmpty
        id="empty-state"
        size="md"
        title="No data available to display. This is a placeholder slot"
        description="This is a placeholder"
        positive-text="Refresh"
        negative-text="Try again"
      />
    </template>
  </HLDataTable>
</HLDataTableWrapper>
ts
import { HLDataTable, HLDataTableWrapper, HLEmpty } from '@platform-ui/highrise'

Highlighted Rows

Highlight specific rows in the table using the highlighted-rows prop. This can be controlled by the highlighted-rows prop.

ID

First Name

Age

Progress

Rating

DOB

Vue
html
<HLContentWrap>
  <HLDataTableWrapper id="highlighted-rows-table-wrapper" max-width="1000px">
    <HLDataTable
      ref="tableInstance"
      :columns="columns"
      :data="data"
      :horizontal-borders="true"
      :vertical-borders="true"
      :row-hover="false"
      :column-hover="false"
      max-height="900px"
      :highlighted-rows="[1,5,8]"
    >
    </HLDataTable>
  </HLDataTableWrapper>
</HLContentWrap>

Row Expand

Expand a row to show more information by handling the @table-row-clicked event.

ID

First Name

Age

Progress

Rating

DOB

html
<HLContentWrap>
  <HLDataTableWrapper>
    <HLDataTable
      ref="tableInstance"
      :columns="columns"
      :data="data"
      :horizontal-borders="true"
      :vertical-borders="true"
      :row-hover="false"
      :column-hover="false"
      max-height="900px"
      @table-row-clicked="handleTableRowClick"
      :expanded-row-renderer="(row)=>h('div',{class: 'bg-gray-200 p-2'}, row.data)"
    >
    </HLDataTable>
  </HLDataTableWrapper>
</HLContentWrap>
ts
const columns = [
  {
    id: 'expand',
    header: '',
    cellFormatter: ({ row }) => {
      return h(
        HLButton,
        {
          variant: 'ghost',
          size: '3xs',
          label: 'Expand',
        },
        { icon: row.getIsExpanded() ? ChevronUpIcon : ChevronDownIcon }
      )
    },
    size: 32,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    cell: ({ row }) => {
      return row.original.id
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      sticky: true,
    },
  },
]

const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    DOB: '2014-01-01',
    expandData: ['This is expanded content for John Doe'],
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    DOB: '2024-01-01',
    expandData: ['This is expanded content for Jane Doe'],
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    DOB: '2024-01-01',
    expandData: ['This is expanded content for Jim Beam'],
  },
])
const handleTableRowClick = (row, rowInfo) => {
  rowInfo.getToggleExpandedHandler()()
}

Row Reorder

Reorder rows by dragging and dropping them. This can be controlled by the row-reordering prop.

ID

First Name

Age

Progress

Rating

DOB

html
<HLContentWrap>
  <HLDataTableWrapper id="row-re-reorder-example">
    <HLDataTable
      id="row-re-reorder-table"
      ref="tableInstance"
      :columns="columns"
      :data="data"
      :horizontal-borders="true"
      :vertical-borders="true"
      max-height="900px"
      :row-reordering="true"
    >
    </HLDataTable>
  </HLDataTableWrapper>
</HLContentWrap>
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: 'Progress',
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    size: 200,
    header: 'Rating',
    meta: {
      headerAlign: 'start',
    },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
      }
      return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
    },
    header: 'DOB',
  },
]

const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    progress: 50,
    rating: 4,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: 'Doe',
    age: 25,
    progress: 75,
    rating: 3,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: 'Beam',
    age: 40,
    progress: 25,
    rating: 2,
    DOB: '2024-01-01',
  },
])

Row Selection

Select rows in the table by checking and can be used when there is CTAs to perform actions on the selected rows.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper>
  <HLDataTable
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :highlight-selected-row="true"
    :selected-rows="selectedRows"
    :row-hover="true"
    max-height="900px"
    row-reordering="true"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const selectedRows = ref<string[]>([])

const toggleRowSelection = (rowId: string, isChecked: boolean) => {
  if (isChecked) {
    selectedRows.value.push(rowId)
  } else {
    selectedRows.value = selectedRows.value.filter(id => id !== rowId)
  }
}

const toggleAllRowsSelection = (isChecked: boolean) => {
  if (isChecked) {
    selectedRows.value = data.value.map(row => row.id)
  } else {
    selectedRows.value = []
  }
}
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 50,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'checkbox',
    header: ({ table }) => {
      return h(HLCheckbox, {
        checked: selectedRows.value.length === data.value.length,
        indeterminate: selectedRows.value.length > 0 && selectedRows.value.length < data.value.length,
        id: 'jkl',
        onUpdateChecked: e => toggleAllRowsSelection(e),
        style: {
          justifyContent: 'center',
        },
      })
    },
    cellFormatter: ({ row }) => {
      return h(HLCheckbox, {
        checked: selectedRows.value.includes(row.original.id),
        id: `select-${row.original.id}`,
        onUpdateChecked: () => toggleRowSelection(row.original.id, !selectedRows.value.includes(row.original.id)),
      })
    },
    size: 50,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
    },
  },
]

Row Striped

Add striped rows to the table to improve readability. This can be controlled by the striped prop.

ID

First Name

Age

Progress

Rating

DOB

Vue
html
<HLDataTableWrapper>
  <HLDataTable
    ref="tableInstance"
    :columns="columns"
    :data="data"
    :striped="true"
    :horizontal-borders="true"
    :vertical-borders="true"
    max-height="900px"
  >
  </HLDataTable>
</HLDataTableWrapper>

Table Cell Borders

Adding state to the data will add a border to the cell.

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="table-cell-borders-table-wrapper" max-width="1000px">
  <HLDataTable
    ref="tableInstance"
    id="table-cell-borders-table"
    :columns="columns"
    :data="cellScenariosData"
    :horizontal-borders="false"
    :vertical-borders="false"
    :page-size="6"
    max-height="900px"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const columns = [
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
]

const data = ref([
  {
    id: 1,
    firstName: { text: 'John', state: 'var(--success-500)' },
    lastName: 'Doe',
    age: 30,
    progress: 50,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: { text: 'Jane', state: 'var(--error-500)' },
    lastName: 'Doe',
    age: 25,
    progress: 75,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: { text: 'Jim', state: 'var(--warning-500)' },
    lastName: 'Beam',
    age: 40,
    progress: 25,
    DOB: '2024-01-01',
  },
])

Table Cell Icon

Add an icon to the table cell by setting the prefixIcon in data.

Last Name

ID

First Name

Age

Progress

Rating

DOB

html
<HLDataTableWrapper id="table-cell-borders-table-wrapper" max-width="1000px">
  <HLDataTable
    ref="tableInstance"
    id="table-cell-borders-table"
    :columns="columns"
    :data="data"
    :horizontal-borders="false"
    :vertical-borders="false"
    :page-size="6"
    max-height="900px"
  >
  </HLDataTable>
</HLDataTableWrapper>
ts
const columns = [
  {
    id: 'lastName',
    header: 'Last Name',
    accessorKey: 'lastName',
    size: 150,
    meta: {
      align: 'left',
      sticky: true,
    },
  },
  {
    id: 'id',
    header: 'ID',
    accessorKey: 'id',
    size: 100,
    meta: {
      headerAlign: 'start',
    },
  },
  {
    id: 'firstName',
    header: 'First Name',
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    meta: {
      align: 'left',
      headerAlign: 'left',
    },
  },
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
]
ts
const data = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: { text: 'Doe', prefixIcon: Wallet01Icon },
    age: 30,
    progress: 50,
    DOB: '2014-01-01',
  },
  {
    id: 2,
    firstName: 'Jane',
    lastName: { text: 'Doe', prefixIcon: Wallet02Icon },
    age: 25,
    progress: 75,
    DOB: '2024-01-01',
  },
  {
    id: 3,
    firstName: 'Jim',
    lastName: { text: 'Beam', prefixIcon: Wallet03Icon },
    age: 40,
    progress: 25,
    DOB: '2024-01-01',
  },
])
ts
import { Wallet01Icon, Wallet02Icon, Wallet03Icon } from '@gohighlevel/ghl-icons/24/outline'

Advanced Usage of Table

Search

Id

Toggle

First Name

Age

Progress

Radio

Rating

DOB

html
<div v-if="showAlert" class="hr-table-header-alerts pb-2">
  <HLAlert
    id="hr-table-header-alert"
    closable
    :title="alertTitle"
    role="alert"
    height="auto"
    width="fit-content"
    color="green"
    @close="showAlert = false"
  >
  </HLAlert>
</div>
<HLDataTableWrapper
  id="data-table-wrapper"
  :column-options="columnOptions"
  :page-size="pageSize"
  :no-of-rows-selected="selectedRows?.length"
  :layout-options="preSetLayouts"
  :freezed-column="freezedColumn"
  :table-height-selected-option="tableHeightSelectedOption"
  :user-driven-value="userDrivenValue"
  :show-global-search="true"
  :show-layout-options="true"
  :show-header="true"
  :selected-table-layout="tableLayout"
  :show-row-layout-option="true"
  :show-column-layout-option="!selectedRows?.length"
  :show-table-height-layout-option="!selectedRows?.length"
  @update:column-reorder="handleReorder"
  @update:column-checked="handleChecked"
  @update:column-freeze="handleFreeze"
  @update:column-unfreeze="handleUnfreeze"
  @update:page-size="updateCustomPageSize"
  @update:table-height-selected-option="setTableHeightSelectedOption"
  @update:row-deselect-all="handleDeselectAll"
  @update:global-filter="handleGlobalFilterChange"
  @update:table-layout="handleSaveLayout"
  @update:table-layout-as="handleSaveAsLayout"
  @update:cancel="handleCancelLayout"
  @update:switch-table-layout="handleSwitchLayout"
  @update:delete-table-layout="handleDeleteLayout"
  @update:current-layout="updateCurrentLayout"
>
  <template #header-content-right>
    <HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="gray">
      Download
      <template #iconLeft>
        <Download01Icon />
      </template>
    </HLButton>
    <HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="gray">
      Share
      <template #iconLeft>
        <Share01Icon />
      </template>
    </HLButton>
    <HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="red">
      Delete
      <template #iconLeft>
        <Trash01Icon />
      </template>
    </HLButton>
    <HLDropdown
      v-if="selectedRows?.length"
      id="header-button-cta"
      width="32px"
      trigger="click"
      placement="bottom"
      :options="[{
        label: 'Download',
        icon: Download01Icon,
      },
      {
        label: 'Share',
        icon: Share01Icon,
      }]"
      :show-arrow="false"
    >
      <HLButton id="header-button-cta" size="2xs" variant="text" color="gray">
        <template #icon>
          <DotsVerticalIcon />
        </template>
      </HLButton>
    </HLDropdown>
    <HLButton v-if="!selectedRows?.length" id="header-button-cta" size="2xs" variant="primary" color="blue"> Button CTA </HLButton>
  </template>
  <template v-if="selectedRows?.length" #header-content-left>
    <HLText size="sm" weight="regular"> {{selectedRows?.length}} rows selected </HLText>
    <HLButton id="header-button-select-all" size="2xs" variant="text" color="blue" @click="handleSelectAll">
      Select All ({{data?.length}})
    </HLButton>
    <HLButton id="header-button-deselect-all" size="2xs" variant="ghost" @click="handleDeselectAll" style="font-size: 14px !important;">
      <template #icon>
        <CloseIcon />
      </template>
    </HLButton>
  </template>
  <HLDataTable
    id="data-table"
    ref="tableInstance"
    :columns="SearchTableColumns"
    :data="searchTableData"
    :striped="true"
    :horizontal-borders="true"
    :vertical-borders="true"
    max-height="900px"
    row-height="40px"
    header-row-height="40px"
    :current-layout="currentLayout"
    @update:column-checked="handleChecked"
    @update:column-order="handleColumnOrder"
    @update:column-clicked="handleColumnClick"
    :selected-rows="selectedRows"
    @no-data="handleNoData"
    :column-order="columnOrder"
    :freezed-columns="{left: freezedColumns}"
    :row-hover="true"
    :column-hover="true"
    :highlight-selected-row="true"
    :row-reordering="true"
    :column-reordering="true"
  >
    <template #no-data>
      <HLEmpty
        id="empty-state"
        size="md"
        title="No data available to display. This is a placeholder slot"
        description="This is a placeholder"
        positive-text="Refresh"
        negative-text="Try again"
        icon="info"
      />
    </template>
  </HLDataTable>
  <template #footer v-if="!noData">
    <HLSpace v-if="table && tableHeightSelectedOption !== 'all'" :size="4" align="center" justify="end">
      <HLPagination
        id="searchable-table-pagination"
        :item-count="table.getFilteredRowModel().rows?.length"
        :per-page="table.getState().pagination.pageSize"
        :current-page="table.getState().pagination.pageIndex + 1"
        :pages-to-display="7"
        :per-page-dropdown-options="paginationOptions"
        size="sm"
        per-page-text="Rows per page"
        @update:per-page="updatePageSize"
        @update:page="
          page => {
            table.setPageIndex(page - 1)
          }
        "
      >
        <template #prev> Previous </template>
        <template #next> Next </template>
      </HLPagination>
    </HLSpace>
    <HLSpace v-if="table && tableHeightSelectedOption === 'all'" :size="4" align="center" justify="center">
      <HLButton size="2xs" variant="secondary" color="gray" @click="handleLoadMore">Load More </HLButton>
    </HLSpace>
  </template>
</HLDataTableWrapper>
ts
import {
  HLDataTable,
  HLDataTableWrapper,
  HLAlert,
  HLSpace,
  HLPagination,
  HLButton,
  HLText,
  HLEmpty,
  HLDropdown,
  HLCheckbox,
  HLProgress,
  HLRadio,
  HLToggle,
  HLTooltip,
} from '@platform-ui/highrise'
import { Download01Icon, Share01Icon, Trash01Icon, DotsVerticalIcon } from '@gohighlevel/ghl-icons/24/outline'
ts
const searchTableData = ref([
  {
    id: 1,
    firstName: 'John',
    lastName: 'Doe',
    age: 25,
    rating: 4,
    status: 'Active',
    progress: 75,
    toggle: true,
    DOB: '2024-01-01',
  },
])

const sortedColumn = ref(null)
const sortDir = ref(null)
const SearchTableColumns = [
  {
    id: 'checkbox',
    header: () => {
      return h(HLCheckbox, {
        checked: selectedRows.value?.length === data.value?.length,
        indeterminate: selectedRows.value?.length > 0 && selectedRows.value?.length < data.value?.length,
        id: 'header-checkbox',
        size: 'xs',
        onUpdateChecked: e => toggleAllRowsSelection(e),
        style: {
          justifyContent: 'center',
        },
      })
    },
    cellFormatter: ({ row }) => {
      return h(HLCheckbox, {
        checked: selectedRows.value.includes(row.original.id),
        id: `select-${row.original.id}`,
        size: 'xs',
        style: {
          justifyContent: 'center',
        },
        onUpdateChecked: () => toggleRowSelection(row.original.id, !selectedRows.value.includes(row.original.id)),
      })
    },
    size: 32,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'id',
    header: 'Id',
    accessorKey: 'id',
    size: 50,
    meta: {
      align: 'center',
    },
  },
  {
    id: 'toggle',
    accessorKey: 'toggle',
    header: 'Toggle',
    size: 70,
    cellFormatter: ({ row }) =>
      h(HLSpace, { align: 'center', wrapItem: false, justify: 'center' }, () =>
        h(HLToggle, {
          id: 'toggle',
          'onUpdate:value': value => {
            row.original.toggle = value
          },
          value: row.original.toggle,
        })
      ),
    meta: {
      align: 'center',
    },
  },
  {
    id: 'firstName',
    header: {
      text: 'First Name',
      icon: UserCircleIcon,
      filterComponent: h(NameFilter, {
        onSetFilter: setFilter,
        onClearFilter: clearFilter,
        onSetSort: setSort,
        sortedColumn,
        sortDir,
        id: 'first-name-filter',
        onResetSort: resetSort,
      }),
    },
    sortingFn: 'alphanumeric',
    accessorKey: 'firstName',
    size: 150,
    cellFormatter: ({ row }) => {
      if (typeof row.original.firstName === 'string') {
        return row.original.firstName
      } else {
        return h(row.original.firstName)
      }
    },
    meta: {
      align: 'left',
    },
  },
  {
    id: 'age',
    header: {
      text: 'Age',
      icon: BarChartCircle01Icon,
      filterComponent: h(AgeFilter, {
        onSetFilter: setFilter,
        onClearFilter: clearFilter,
        onSetSort: setSort,
        sortedColumn,
        sortDir,
        id: 'age-filter',
        onResetSort: resetSort,
      }),
    },
    accessorKey: 'age',
    sortingFn: 'alphanumeric',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
  },
  {
    id: 'progress',
    header: { text: 'Progress', icon: UserCircleIcon },
    accessorKey: 'progress',
    sortingFn: 'alphanumeric',
    size: 250,
    cellFormatter: ({ row }) => {
      return h(HLProgress, {
        id: 'progress',
        percentage: row.original.progress,
        type: 'line',
        dashboardSize: 'sm',
        valuePlacement: 'outside',
      })
    },
    meta: {
      headerAlign: 'left',
    },
  },
  {
    id: 'radio',
    header: 'Radio',
    size: 70,
    cellFormatter: ({ row }) => {
      return h(HLRadio, {
        id: 'radio',
        value: row.original.id,
        size: 'xs',
        onChange: () => {
          selectedRadio.value = row.original.id
        },
        checked: selectedRadio.value === row.original.id,
      })
    },
    meta: {
      align: 'center',
    },
  },
  {
    id: 'rating',
    accessorKey: 'rating',
    header: { text: 'Rating', icon: Star01Icon },
    cellFormatter: ({ row }) => {
      const rating = row.original.rating
      const stars = []
      for (let i = 0; i < 5; i++) {
        const fill = i < rating ? 'var(--yellow-500)' : 'var(--gray-400)'
        stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon))
      }
      return h(
        HLTooltip,
        { trigger: 'hover', variant: 'dark', placement: 'top-start' },
        {
          trigger: h('div', { class: 'flex items-center gap-2 cursor-pointer' }, stars),
          default: h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, [
            h(HLIcon, { size: '20', color: 'white' }, InfoCircleIcon),
            h(HLText, { size: 'sm', weight: 'semibold' }, row.original.rating),
          ]),
        }
      )
    },
    meta: {
      headerAlign: 'left',
    },
  },
  {
    id: 'DOB',
    accessorKey: 'DOB',
    meta: {
      align: 'right',
      headerAlign: 'right',
    },
    header: {
      text: 'DOB',
      icon: CalendarIcon,
      filterComponent: h(DateFilter, {
        onSetFilter: setFilter,
        onClearFilter: clearFilter,
        onSetSort: setSort,
        sortedColumn,
        sortDir,
        id: 'date-filter',
        onResetSort: resetSort,
      }),
    },
  },
]
const freezedColumns = ref(['checkbox'])
const columnOptions = ref([
  {
    value: 'id',
    label: 'Id',
    checked: true,
  },

  {
    value: 'toggle',
    label: 'Toggle',
    checked: true,
    icon: Wallet01Icon,
  },
  {
    value: 'firstName',
    label: 'First Name',
    checked: true,
    icon: Wallet01Icon,
  },
  {
    value: 'age',
    label: 'Age',
    checked: true,
    icon: Wallet01Icon,
  },
  {
    value: 'progress',
    label: 'Progress',
    checked: true,
    icon: BarChartCircle01Icon,
  },
  {
    value: 'radio',
    label: 'Radio',
    checked: true,
  },
  {
    value: 'rating',
    label: 'Rating',
    checked: true,
    icon: Star01Icon,
  },
  {
    value: 'DOB',
    label: 'DOB',
    checked: true,
    icon: CalendarDateIcon,
  },
])
const paginationOptions = ref([
  {
    key: 50,
    label: 50,
  },
  {
    key: 100,
    label: 100,
  },
  {
    key: 200,
    label: 200,
  },
  {
    key: 300,
    label: 300,
  },
])

const preSetLayouts = ref([
  {
    value: 'custom',
    label: 'Custom',
  },
  {
    value: 'all',
    label: 'All',
  },
])
const selectedRadio = ref(null)
function handleSaveAsLayout(value) {
  preSetLayouts.value.push({
    value: value,
    label: value,
  })
  showAlert.value = true
  alertTitle.value = `${value} Layout saved successfully`
}
function handleSaveLayout() {
  showAlert.value = true
  alertTitle.value = `${tableLayout.value.label || 'Default'} Layout saved successfully`
}
let backUpLayoutColumnData = {
  columnOptions: [],
  freezedColumn: {},
}
let backUpLayoutTableHeight = {
  tableHeightSelectedOption: '',
  pageSize: '',
  userDrivenValue: '',
}

function handleCancelLayout(value) {
  if (previousLayout === 'columns') {
    columnOptions.value = [...backUpLayoutColumnData.columnOptions]

    // Handle visibility for each column
    columnOptions.value.forEach(option => {
      const tableColumn = tableInstance.value.table?.getColumn(option.value)
      if (tableColumn?.getIsVisible() !== option.checked) {
        tableColumn?.toggleVisibility(option.checked)
      }
    })

    // Handle column order
    const columnIdsInOrder = columnOptions.value.map(option => option.value)
    tableInstance.value.table?.setColumnOrder(columnIdsInOrder)
    // Handle frozen columns
    if (backUpLayoutColumnData?.freezedColumn?.value && backUpLayoutColumnData?.freezedColumn?.value !== 'None') {
      const columnIds = ['checkbox', ...columnOptions.value.map(option => option.value)]
      freezedColumn.value = { ...backUpLayoutColumnData.freezedColumn }
      const columnIndex = columnIds.indexOf(backUpLayoutColumnData.freezedColumn.value)
      tableInstance.value.table?.setColumnPinning({
        left: columnIds.slice(0, columnIndex + 1),
        right: [],
      })
    } else {
      freezedColumn.value = {
        value: 'None',
        label: 'None',
      }
      tableInstance.value.table?.setColumnPinning({
        left: ['checkbox'],
        right: [],
      })
    }
  }
  if (previousLayout === 'tableHeight') {
    tableHeightSelectedOption.value = backUpLayoutTableHeight.tableHeightSelectedOption
    pageSize.value = backUpLayoutTableHeight.pageSize
    userDrivenValue.value = backUpLayoutTableHeight.userDrivenValue
    if (backUpLayoutTableHeight.tableHeightSelectedOption === 'userDriven') {
      tableInstance.value.table?.setPageSize(backUpLayoutTableHeight.userDrivenValue)
    } else {
      tableInstance.value.table?.setPageSize(pageSize.value)
    }
  }
}
function handleGlobalFilterChange(value) {
  table.value.setGlobalFilter(String(value))
}

const handleColumnClick = columnId => {
  if (columnId === 'firstName' || columnId === 'DOB' || columnId === 'age') {
    if (table.value.getState()?.sorting[0]?.id === columnId) {
      const desc = table.value.getState()?.sorting[0]?.desc
      if (desc) {
        sortedColumn.value = ''
        sortDir.value = null
        table.value.setSorting([])
      } else {
        sortedColumn.value = columnId
        sortDir.value = 'desc'
        table.value.setSorting([{ id: columnId, desc: true }])
      }
    } else {
      sortedColumn.value = columnId
      sortDir.value = 'asc'
      table.value.setSorting([{ id: columnId, desc: false }])
    }
  }
}
function setSort(id, desc) {
  sortedColumn.value = id
  sortDir.value = desc ? 'desc' : 'asc'
  table.value.setSorting([{ id, desc }])
}
function resetSort() {
  sortedColumn.value = null
  sortDir.value = null
  table.value.setSorting([])
}
function setFilter(id, filterFn, value) {
  const column = table.value.getColumn(id)
  column.columnDef.filterFn = filterFn
  column.setFilterValue(value)
}
function clearFilter(id) {
  const column = table.value.getColumn(id)
  column.setFilterValue(undefined)
}

const handleColumnOrder = columnIds => {
  columnIds.shift()
  columnOptions.value = columnIds.map(id => columnOptions.value.find(option => option.value === id))
}
const handleReorder = (...args) => {
  columnOptions.value = args[0]
  const columnIds = columnOptions.value.filter(option => option.checked !== false).map(option => option.value)
  tableInstance.value.table?.setColumnOrder(columnIds)
}
const handleChecked = (...args) => {
  const [field, checked] = args
  const column = columnOptions.value.find(option => option.value === field)
  column.checked = checked
  const tableColumn = tableInstance.value.table?.getColumn(field)
  tableColumn?.toggleVisibility(checked)
}
const freezedColumn = ref()
const handleFreeze = item => {
  freezedColumn.value = item
  const columnIds = ['checkbox', ...columnOptions.value.map(option => option.value)]
  const columnIndex = columnIds.indexOf(item.value)
  tableInstance.value.table?.setColumnPinning({
    left: columnIds.slice(0, columnIndex + 1),
    right: [],
  })
}
const handleUnfreeze = () => {
  columnOptions.value.forEach(option => {
    option.frozen = false
  })
  tableInstance.value.table?.setColumnPinning({
    left: ['checkbox'],
    right: [],
  })
}
const pageSize = ref(20)
const tableHeightSelectedOption = ref('default')
const userDrivenValue = ref(null)
const updateCustomPageSize = userDrivenNumber => {
  userDrivenValue.value = userDrivenNumber
  tableInstance.value.table?.setPageSize(userDrivenNumber)
  // add in pagination options with ascending order
  paginationOptions.value = paginationOptions.value.filter(option => option.key !== userDrivenNumber)
  paginationOptions.value.push({
    key: userDrivenNumber,
    label: userDrivenNumber,
  })
  paginationOptions.value.sort((a, b) => a.key - b.key)
}
const setTableHeightSelectedOption = value => {
  tableHeightSelectedOption.value = value
  if (tableHeightSelectedOption.value === 'default') {
    tableInstance.value.table?.setPageSize(pageSize.value)
  }
}
const updatePageSize = value => {
  tableHeightSelectedOption.value = 'default'
  pageSize.value = Number(value)
  tableInstance.value.table?.setPageSize(pageSize.value)
}
const handleLoadMore = () => {
  tableInstance.value.table?.setPageSize(table.value.getState().pagination.pageSize + 50)
}

const handleDeselectAll = () => {
  selectedRows.value = []
}
const handleSelectAll = () => {
  selectedRows.value = data.value.map(row => row.id)
}

const currentLayout = ref('')
let previousLayout = ''
const updateCurrentLayout = value => {
  if (value) previousLayout = value
  currentLayout.value = value
  if (value === 'columns') {
    backUpLayoutColumnData = {
      columnOptions: columnOptions.value.map(option => ({
        ...option,
        icon: option.icon, // Preserve the icon reference directly
      })),
      freezedColumn: freezedColumn.value
        ? {
            ...freezedColumn.value,
            icon: freezedColumn.value.icon, // Preserve the icon reference if it exists
          }
        : null,
    }
  } else if (value === 'tableHeight') {
    backUpLayoutTableHeight = {
      tableHeightSelectedOption: tableHeightSelectedOption.value,
      pageSize: pageSize.value,
      userDrivenValue: userDrivenValue.value,
    }
  }
}
const noData = ref(false)
const handleNoData = value => {
  noData.value = value
}

const tableLayout = ref({
  value: 'default',
  label: 'Default',
})
const handleSwitchLayout = value => {
  tableLayout.value = value
  handleReorder(columnOptions.value.reverse())
}
const columnOrder = computed(() => ['checkbox', ...columnOptions.value.map(option => option.value)])
const handleDeleteLayout = value => {
  if (value.value === tableLayout.value.value) {
    tableLayout.value = {
      value: 'default',
      label: 'Default',
    }
  }
  preSetLayouts.value = preSetLayouts.value.filter(option => option.value !== value.value)
  showAlert.value = true
  alertTitle.value = `${value.value} Layout deleted successfully`
}
const alertTitle = ref('')
const showAlert = ref(false)

Imports

ts
import { HLDataTable, HLDataTableWrapper } from '@platform-ui/highrise'

Props

DataTable

NameTypeDefaultDescription
id *string | undefinedundefinedUnique identifier for the table
columns *DataTableColumn[][]Columns (headers)
data *any[][]Data (rows)
maxHeightstring'800px'Maximum height of the table
rowHeightstring'36px'Height of the row
headerRowHeightstring'36px'Height of the header row
stripedbooleanfalseApplies alternating background colors to rows
horizontalBordersbooleantrueShow horizontal borders
verticalBordersbooleantrueShow vertical borders
rowHoverbooleantrueApplies hover effect to table rows
columnHoverbooleantrueApplies hover effect to table columns
highlightSelectedRowbooleanfalseHighlight selected row
highlightedRowsstring[][]Predefined highlighted rows
rowReorderingbooleanfalseEnable row reordering
columnReorderingbooleantrueEnable column reordering
freezedColumns{ left: string[], right: string[] }{ left: [], right: [] }Pins columns to the left or right side of the table
columnResizingbooleantrueEnable column resizing
selectedRowsstring[][]Array of selected row IDs
currentLayout'rows' | 'columns' | 'tableHeight' | undefinedundefinedCurrent layout mode
columnOrderstring[] | undefinedundefinedColumn order
rowClickablebooleanfalseEnable row clickable
rowUniqueIdstring'id'Unique identifier for the row
expandedRowRenderer(row: DataTableRow) => VNodeChildnullRenderer for expanded rows
loadingboolean | { status: boolean; skeletonRows: number }falseShow loading state, default skeletonRows is 10
pageSizenumber20Number of rows per page
tableBodyMinHeightstring | undefinedundefinedMinimum height of the table body
restrictRowReorderingUptonumber | undefinedundefinedExclude rows reordering from 0 to this index

DataTableWrapper

NameTypeDefaultDescription
id *string | undefinedundefinedUnique identifier for the table
showHeaderbooleanfalseShow header
showLayoutOptionsbooleanfalseShow layout options
showRowLayoutOptionbooleanfalseShow row layout option
showColumnLayoutOptionbooleanfalseShow column layout option
showTableHeightLayoutOptionbooleanfalseShow table height layout option
showGlobalSearchbooleanfalseShow global search
searchPlaceholderstringSearchPlaceholder text for the global search input field
columnOptionsColumnOption[][]DataTableColumn options
freezedColumnColumnOption | undefinedundefinedFreezed column
layoutOptionsLayoutOption[][]Layout options
selectedTableLayoutLayoutOption | undefinedundefinedSelected table layout
noOfRowsSelectednumber | undefinedundefinedNumber of rows selected
pageSizenumber | undefinedundefinedNumber of rows per page
tableHeightSelectedOptionstring'default'Table height selected option
userDrivenValuenumber | nullnullUser driven value
maxWidthstring'800px'Maximum width of the table
responsiveColumnWidthbooleanfalseEnable responsive column width, if this is set true then size of each column will rendered in %
globalFilterstring | undefinedundefinedGlobal filter value
fillParentHeightboolean | undefinedundefinedOccupy the full height of the parent container

Types

ts
export interface DataTableColumn {
  id: string
  header:
    | string
    | {
        text: string
        icon?: VNodeChild
        filterComponent?: VNodeChild
      }
    | VNodeChild
  accessorKey?: string
  size?: number // if responsiveColumnWidth is `true` then size will treated as `%` else in `pixels`
  minSize?: number // min width of the column in pixels
  maxSize?: number // max width of the column in pixels
  filterFn?: FilterFns
  sortingFn?: SortingFn
  cellFormatter?: (row: DataTableRow<any>) => VNodeChild
  meta?: {
    align?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
    headerAlign?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
  }
}
ts
export interface ColumnOption {
  value: string
  label: string
  checked?: boolean
  frozen?: boolean
}
ts
export interface LayoutOption {
  value: string
  label: string
}

Emits

DataTable

NameParametersDescription
@no-data(isEmpty: boolean)Emitted when table has no data
@table-row-clicked(row: DataTableRow, rowInfo: RowInfo)Emitted when row is clicked
@update:column-checked(columnId: string, checked: boolean)Emitted when column visibility changes
@update:column-clicked(columnId: string)Emitted when column is clicked
@update:column-order(order: string[])Emitted when column order changes
@update:row-reordered(draggedRowIndex: number, targetIndex: number)Emitted when row is reordered

DataTableWrapper

NameParametersDescription
@update:cancel(layout: string)Emitted when table layout is cancelled
@update:column-checked(columnId: string, checked: boolean)Emitted when column visibility changes
@update:column-freeze(columnId: string)Emitted when column is frozen
@update:column-reorder(columnIds: string[])Emitted when columns are reordered
@update:column-unfreeze(columnId: string)Emitted when columns are unfrozen
@update:current-layout(layout: string)Emitted when current layout changes
@update:delete-table-layout(layout: string)Emitted when table layout is deleted
@update:global-filter(filter: string)Emitted when global filter changes
@update:page-size(pageSize: number)Emitted when page size changes
@update:row-deselect-all(rowIds: string[])Emitted when all rows are deselected
@update:switch-table-layout(layout: string)Emitted when table layout is switched
@update:table-height-selected-option(option: string)Emitted when table height selected option changes
@update:table-layout-as(layout: string)Emitted when table layout is saved as
@update:table-layout(layout: string)Emitted when table layout changes

Slots

DataTable

NameDescription
no-dataWhen table has no data

DataTableWrapper

NameDescription
headerHeader
footerFooter
header-content-leftHeader content left (left side of the header)
header-content-rightHeader content right (right side of the header)
defaultThe default content slot for table content