Skip to content

Table

Basic Usage

Customize your content here!
Role Name
Date
Status
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
Success
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
vue
<template>
  <spr-table action :headers="headers" :data-table="data">
    <div>Customize your content here!</div>
    <template #action="{ row }">
      <spr-lozenge :label="row.status.title" :tone="row.status.title.toLowerCase()" />
    </template>
    <template #action-name> Status </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);
</script>

No Slot and Action

Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table :headers="headers" :data-table="data" />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);
</script>

Table Actions

The implementation for the search field here is to emit and trigger an API call to update the data-table

Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table
    action
    :headers="headers"
    :data-table="data"
    :table-actions="tableActions"
    v-model:searchModel="searchModel"
  />
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';

const headers = ref([
  // Fill this with your headers
]);

const data = ref([
  // Fill this with your data
]);

const tableActions = ref({
  // Toggle this to true if you need search field
  search: true,
  // Toggle this to t rue if you need filter field
  filter: true,
  // Toggle this to true if you need option field
  option: true,
});

const searchModel = ref('');

watch(searchModel, (newValue) => {
  console.log(newValue);
  // On change of the search variable trigger a fetch
  // fetchApiHere();
});
</script>

Table Action Slot

Table Name
table description
Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table
    action
    :headers="headers"
    :data-table="data"
    :table-actions="tableActions"
    v-model:searchModel="searchModel"
  >
    <div>
      <div class="spr-text-color-strong spr-font-size-400 spr-font-weight-medium">Table Name</div>
      <div>table description</div>
    </div>
    <template #tableActionSection>
      <spr-button>Button</spr-button>
    </template>
  </spr-table>
</template>

Multi Select

Allows the selection of multiple or all rows in the table.

WARNING

  • If table is paginated, only the rows on the current page will be selected.
  • selectedKeyId prop must be provided.
  • title value of the table data object mapped with selectedKeyId in the table data must be unique.

TIP

Set the prop returnCompleteSelectedProperties to false to return the selected data as an array of objects with the selectedKeyId value only.

[]
Role Name
Date
Status
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
Success
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
vue
<template>
  <spr-table
    action
    :headers="headers"
    :data-table="data"
    variant="white"
    :is-multi-select="true"
    :selected-key-id="'name'"
    :return-complete-selected-properties="false"
    @update:selectedData="handleSelectedData"
  >
    <template #action="{ row }">
      <spr-lozenge :label="row.status.title" :tone="row.status.title.toLowerCase()" />
    </template>
    <template #action-name> Status </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const selectedTableData = ref([]);
const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);
const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
    {
    name: {
      title: 'Shift 1',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
      {
    name: {
      title: 'Shift 2',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
      {
    name: {
      title: 'Shift 3',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
      {
    name: {
      title: 'Shift 4',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
      {
    name: {
      title: 'Shift 5',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);

const handleSelectedData = (data) => {
  // do anything with the selected data
  selectedTableData.value = [...data];
}
</script>

Custom Column

You can customize the column of the table by using the dynamically named slot column field (the field property in the headers object).

Role Name
Date
Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis autem minus fugiat culpa ad magnam nisi ex facilis ducimus sit neque modi porro optio cupiditate iusto, blanditiis asperiores aperiam veritatis mollitia laboriosam? Consectetur, deserunt? Reprehenderit ipsa, debitis eaque accusamus ducimus quasi deserunt laborum asperiores ea, nemo, optio corporis rerum! Veniam ex voluptatibus eveniet consequuntur saepe doloribus sint laboriosam eligendi sequi esse vero, quam consectetur iste inventore aliquam soluta quibusdam at perferendis ratione, voluptatum accusantium amet dignissimos perspiciatis. Aspernatur, voluptate amet.
Lorem ipsectetur adipiscing elit.
Nov 30, 2025
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table
    action
    :headers="headers"
    :data-table="data"
  >
    <template #name="{ row }"> //slot name is the field property value in the headers object
      <spr-tooltip :has-max-width="true" :text="String(row.name.title)">
        <div 
          ref="rowTitleRef" 
          class="spr-truncate spr-text-sm" 
          :style="`max-width: ${getCellMaxWidth}`"
        >
          {{ row.name.title }}
        </div>
      </spr-tooltip>
      <span class="spr-text-color-base spr-text-xs spr-font-normal">{{ row.name.subtext }}</span>
    </template>
    <template #action>
      <div class="spr-flex spr-flex-auto spr-justify-end spr-gap-2">
        <spr-button variant="primary" tone="success" size="small"> Action 1 </spr-button>
        <spr-button variant="primary" tone="danger" size="small"> Action 2 </spr-button>
      </div>
    </template>
  </spr-table>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';

const rowTitleRef = ref<HTMLElement|null>(null);
const cellWidth = ref(0)

const getCellMaxWidth = computed(() => {
  return cellWidth.value > 0 ? `${cellWidth.value}px` : '100%'
})

const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis autem minus fugiat culpa ad magnam nisi ex facilis ducimus sit neque modi porro optio cupiditate iusto, blanditiis asperiores aperiam veritatis mollitia laboriosam? Consectetur, deserunt? Reprehenderit ipsa, debitis eaque accusamus ducimus quasi deserunt laborum asperiores ea, nemo, optio corporis rerum! Veniam ex voluptatibus eveniet consequuntur saepe doloribus sint laboriosam eligendi sequi esse vero, quam consectetur iste inventore aliquam soluta quibusdam at perferendis ratione, voluptatum accusantium amet dignissimos perspiciatis. Aspernatur, voluptate amet.',
      subtext: 'Lorem ipsectetur adipiscing elit.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
    {
    name: {
      title: 'Shift 1',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 01, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Pending',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  }
]);

</script>

Custom Tailwind Classes for Headers

You can customize the header cells with Tailwind CSS classes by using the customTailwindClasses property in the header configuration.

WARNING

Utilizing this field will remove all of the existing tailwind classes on the header only (th) the classes of the main DIV which holds the name, icon and badge will still take effect. Additionally, the only tested tailwind classes for this is the spr- tailwind classes. If you're using your own tailwind configurations, the component may not behave as expected. Additionally, changing the class of the multi-select header is currently not supported.

Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table :headers="headers" :data-table="data">
    <template #header="{ column }">
      <div :class="column.customTailwindClasses">
        {{ column.name }}
      </div>
    </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headers = ref([
  { 
    field: 'name', 
    name: 'Role Name', 
    sort: true, 
    hasAvatar: true, 
    hasSubtext: true, 
    customTailwindClasses: 'spr-bg-tomato-500' 
  },
  { 
    field: 'lastUpdate', 
    name: 'Date', 
    sort: true, 
    hasAvatar: false, 
    hasSubtext: false, 
    customTailwindClasses: 'spr-bg-blueberry-500' 
  },
]);
</script>

The implementation for the pagination here is to emit and trigger an API call to update the data-table

Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
21 - 40 of 100
vue
<template>
  <spr-table action :headers="headers" :data-table="data">
    <template #footer>
      <spr-table-pagination
        v-model:selected-row-count="selectedRowCount"
        :total-items="totalItems"
        :current-page="currentPage"
        :dropdown-selection="dropdownSelection"
        @previous="handlePrevious"
        @next="handleNext"
        :version="'backend'"
      />
    </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';

const headers = ref([
  // Fill this with your headers
]);

const data = ref([
  // Fill this with your data
]);

const totalItems = ref(100);
const currentPage = ref(2);
const dropdownSelection = [
  { text: 10, value: 10 },
  { text: 20, value: 20 },
  { text: 30, value: 30 },
];

const selectedRowCount = ref(10);

watch(selectedRowCount, (newValue) => {
  console.log('Selected Row Count:', newValue);
  // On change of the selectedRowCount variable trigger a fetch
  // fetchApiHere();
});

const handlePrevious = () => {
  console.log('clicked previous');
  if (currentPage.value > 1) {
    currentPage.value--;
  }

  fetchItems(currentPage.value);
};

const handleNext = () => {
  console.log('clicked next');

  if (currentPage.value * selectedRowCount.value < totalItems.value) {
    currentPage.value++;
  }

  fetchItems(currentPage.value);
};

const fetchItems = computed((page) => {
  const response = apiCall(page);

  data.value = parse(response);
});
</script>

Cells

Lozenge

You can change the title of a cell to a lozenge

Customize your content here!
Role Name
Date
avatar
Active
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
avatar
Lozenge
vue
<template>
  <spr-table action :headers="headersLozenge" :data-table="dataLozenge" variant="surface" @onSort="handleSort">
    <div>Customize your content here!</div>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const headersLozenge = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: false, hasSubtext: true, hasLozengeTitle: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false, hasLozengeTitle: true },
]);

const lozengeTitle = {
  title: 'Active',
  tone: 'success',
  avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
  fill: true,
  lozengeIcon: 'ph:building'
}

const lozengeSecondTitle = {
  title: 'Lozenge',
  avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
}

const dataLozenge = [
  {
    name: {
      title: lozengeTitle,
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
      lozengeIcon: 'ph:user',
    },
    lastUpdate: {
      title: lozengeSecondTitle,
      subtext: 'Lorem ipsum dolor ',
      lozengeFill: true,
      lozengeAvatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
];
</script>

Chips

You can change the title of a cell to a chip

Customize your content here!
Role Name
Date
Avatar
Active
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Avatar
Second Title
2
vue
<template>
<spr-table action :headers="headersChips" :data-table="dataChips" variant="surface" @onSort="handleSort">
  <div>
    Customize your content here!
  </div>
</spr-table>
</template>

<script setup lang='ts'>
import { ref } from 'vue';
const headersChips = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: false, hasSubtext: true, hasChipTitle: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false, hasChipTitle: true },
])

const chipsTitle = {
  title: 'Active',
  icon: 'ph:building',
  iconWeight: 'regular',
  avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
  avatarVariant: 'image',
}

const chipsSecondTitle = {
  title: 'Second Title',
  icon: 'ph:users-three',
  iconWeight: 'regular',
  badge: true,
  badgeText: '2',
  badgeVariant: 'brand',
  avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
  avatarVariant: 'image',
}

const dataChips = [
  {
    name: {
      title: chipsTitle,
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
      lozengeIcon: 'ph:user'
    },
    lastUpdate: {
      title: chipsSecondTitle,
      subtext: 'Lorem ipsum dolor ',
      lozengeFill: true,
      lozengeAvatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg'
    },
  },
]
</script>

Multiple Chips and Lozenges

You can change the title to multiple chips and lozenges

Customize your content here!
Role Name
Date
avatar
First
avatar
Second
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Avatar
Active
2
Avatar
Inactive
vue
<template>
<spr-table action :headers="headersMultiple" :data-table="dataMultiple" variant="surface" @onSort="handleSort">
  <div>
    Customize your content here!
  </div>
</spr-table>
</template>

<script setup lang='ts'>
import { ref } from 'vue';

const headersMultiple = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: false, hasSubtext: true, hasLozengeTitle: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false, hasChipTitle: true },
])

const lozengeCell =  {
    title: 'Active',
    tone: 'success',
    avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    fill: true,
    lozengeIcon: 'ph:building',
};
const chipCell = [
  {
    title: 'Active',
    icon: 'ph:building',
    iconWeight: 'regular',
    badge: true,
    badgeText: '2',
    badgeVariant: 'brand',
    avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    avatarVariant: 'image',
  },
  {
    title: 'Active',
    icon: 'ph:building',
    iconWeight: 'regular',
    badge: true,
    badgeText: '2',
    badgeVariant: 'brand',
    avatarUrl: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    avatarVariant: 'image',
  }
]

const dataMultiple = [
  {
    name: {
      title: lozengeCell,
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: chipCell,
      subtext: 'Lorem ipsum dolor ',
      chipTitle: chipCell
    },
  },
]
</script>

Variant

You can customize the header background of the table using the variant attribute. The available options are white and surface.

Customize your content here!
Role Name
Date
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
vue
<template>
  <spr-table action :headers="headers" :data-table="data">
    <div>Customize your content here!</div>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);

const handleSort = (e) => {
  console.log('sort', e);
};
</script>

Sorting

Customize your content here!
Role Name
Date
Status
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
Success
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
vue
<template>
  <spr-table action :headers="headers" :data-table="data" variant="white" @onSort="getSortOrder()" sortOrder>
    <div>Customize your content here!</div>
    <template #action="{ row }">
      <spr-lozenge :label="row.status.title" :tone="row.status.title.toLowerCase()" />
    </template>
    <template #action-name> Status </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headers = ref([
  { field: 'name', name: 'Role Name', sort: true, hasAvatar: true, hasSubtext: true },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);

const sortOrder = ref('asc');
const getSortOrder = () => {
  sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
  console.log(sortOrder.value);
  return sortOrder.value;
};
</script>

Badge

Badges can have different color schemes to indicate various statuses, such as disabled, brand, danger, or information.

Customize your content here!
Role Name
1
Date
2
Status
S
Shift
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 30, 2025
Success
S1
Shift 1
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S2
Shift 2
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S3
Shift 3
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S4
Shift 4
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
S5
Shift 5
Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.
Nov 01, 2025
Pending
vue
<template>
  <spr-table action :headers="headersWithBadge" :data-table="data">
    <div>Customize your content here!</div>
    <template #action="{ row }">
      <spr-lozenge :label="row.status.title" :tone="row.status.title.toLowerCase()" />
    </template>
    <template #action-name> Status </template>
  </spr-table>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const headersWithBadge = ref([
  {
    field: 'name',
    name: 'Role Name',
    sort: true,
    hasAvatar: true,
    hasSubtext: true,
    badgeText: '1',
    badgeVariant: 'brand',
  },
  { field: 'lastUpdate', name: 'Date', sort: true, hasAvatar: false, hasSubtext: false },
]);

const data = ref([
  {
    name: {
      title: 'Shift',
      subtext: 'Lorem ipsectetur adipiscing elit. Sed etiam, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    lastUpdate: {
      title: 'Nov 30, 2025',
      subtext: 'Lorem ipsum dolor ',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
    status: {
      title: 'Success',
      subtext: 'Lorem ipsum dolor sit amet, consectetur, sed etiam.',
      image: 'https://media.sproutsocial.com/uploads/2022/06/profile-picture.jpeg',
    },
  },
]);
</script>

API Reference

Props

NameDescriptionTypeDefault
actionEnables an action column at the end of the table. When set to true, it allows custom content to be placed in an action column for each row.booleanfalse
dataTableArray of data objects to display in the table. Each object represents a row, with properties corresponding to columns defined in the headers.Array<TableData>[]
headersArray of header configuration objects defining the columns of the table. Each header object includes field name, display name, and various display options.Array<Header>[]
emptyStateConfiguration for the empty state displayed when the table has no data. Customize description, sub-description, image, and size.EmptyState{ description: 'No results found', subDescription: 'Try a different search term', image: 'location', size: 'large' }
loadingWhen set to true, displays a loading state instead of the table data or empty state.booleanfalse
tableActionsConfiguration for table actions displayed above the table. Includes options for search, filter, and additional options.{ search: boolean, filter: boolean, option: boolean }{ search: false, filter: false, option: false }
searchModelUsed with v-model:searchModel to bind the search input value. When using the search feature, this prop enables two-way binding.string''
sortOrderDefault sort order for the table. Can be 'asc' (ascending) or 'desc' (descending).'asc' | 'desc''asc'
variantControls the background color of the table header. Options are 'white' and 'surface'.'surface' | 'white''surface'
isRowClickableWhen true, enables clicking on rows to trigger the onRowClick event. Useful for interactive tables where rows can be selected or expanded.booleanfalse
fullHeightWhen true, the table will take up the full available height of its container.booleantrue
removeHeaderOnEmptyWhen true, the table headers will not be displayed if the table has no data.booleanfalse
isMultiSelectEnables multi-select functionality with checkboxes in the first column of the table.booleanfalse
selectedKeyIdSpecifies which key in the table data object will be used as an identifier for selected rows. Required when using isMultiSelect.string''
returnCompleteSelectedPropertiesControls the structure of the data emitted when rows are selected. If true, the full row data object is emitted; if false, only the value of the property specified by selectedKeyId is emitted.booleanfalse

Events

NameDescriptionParameters
update:searchModelEmitted when the search input value changes. Used with v-model:searchModel for two-way binding.(value: string)
onSortEmitted when a sortable column header is clicked. Provides the field name and the new sort order.({ field: string, sortOrder: 'asc' | 'desc' })
onRowClickEmitted when a row is clicked (if isRowClickable is true). Provides the row data and row index.(rowData: TableData, rowIndex: number)
onHoverEmitted when the mouse enters or leaves a row. Provides the active state and the row data.({ active: boolean, data: TableData })
update:selectedDataEmitted when row selection changes in multi-select mode. Provides an array of selected items based on the returnCompleteSelectedProperties setting.(selectedItems: TableData[] | any[])

Slots

NameDescriptionProps
defaultContent displayed above the table. Typically used for a title or description.-
tableActionSectionCustom content for the table actions area. Typically used to add buttons or other controls next to the search, filter, and options.-
actionContent for the action column in each row. Only available when the action prop is true.{ row: TableData }
action-nameHeader content for the action column. Only available when the action prop is true.-
empty-stateCustom content to display when the table has no data.-
loadingCustom loading state content. Displayed when the loading prop is true.-
footerContent displayed at the bottom of the table. Typically used for pagination or summary information.-
[field]Dynamic slots based on the field names in the headers. Allows custom rendering for specific columns.{ row: TableData, rowIndex: number }

Header Object Properties

NameDescriptionTypeRequired
fieldUnique identifier for the column. Maps to the property name in the data objects.stringYes
nameDisplay name for the column header.stringYes
sortWhether the column is sortable.booleanNo
hasAvatarWhether to display an avatar in the column cells. If true, the data object should include an image property.booleanNo
hasIconWhether to display an icon in the column cells. If true, the data object should include an icon property.booleanNo
hasSubtextWhether to display subtext in the column cells. If true, the data object should include a subtext property.booleanNo
hasLozengeTitleWhether to display the title as a lozenge. If true, the title property should be a LozengeTitle object or array.booleanNo
hasChipTitleWhether to display the title as a chip. If true, the title property should be a ChipTitle object or array.booleanNo
badgeTextText to display in a badge next to the column header.stringNo
badgeVariantVariant of the badge to display next to the column header. Options include 'disabled', 'brand', 'danger', and 'information'.stringNo
avatarVariantVariant of the avatar to display in the column cells. Used with hasAvatar.stringNo
customTailwindClassesCustom Tailwind CSS classes to apply to the column cells.stringNo

Product Uses

Sprout HR
Sprout Ecosystem
Sprout Sidekick