ListView

Displays data in a structured, scrollable list with support for columns, groups, and custom content. Makes large sets of information easy to view, select, and interact with.

Simple List

Name
Email
Role
Status
vue
<script setup>
import { h, reactive } from 'vue'

import { Avatar, ListView } from 'frappe-ui'

const columns = reactive([
  {
    label: 'Name',
    key: 'name',
    width: 3,
    getLabel: ({ row }) => row.name,
    prefix: ({ row }) =>
      h(Avatar, { shape: 'circle', image: row.user_image, size: 'sm' }),
  },
  { label: 'Email', key: 'email', width: '200px' },
  { label: 'Role', key: 'role' },
  { label: 'Status', key: 'status' },
])

const rows = [
  {
    id: 1,
    name: 'John Doe',
    email: 'john@doe.com',
    status: 'Active',
    role: 'Developer',
    user_image: 'https://avatars.githubusercontent.com/u/499550',
  },
  {
    id: 2,
    name: 'Jane Doe',
    email: 'jane@doe.com',
    status: 'Inactive',
    role: 'HR',
    user_image: 'https://avatars.githubusercontent.com/u/499120',
  },
]
</script>

<template>
  <ListView
    class="h-[150px]"
    :columns="columns"
    :rows="rows"
    :options="{
      getRowRoute: (row) => ({
        name: 'User',
        params: { userId: row.id },
      }),
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
    }"
    row-key="id"
  />
</template>

Custom List

Name
Email
Role
Status
vue
<script setup>
import { reactive } from 'vue'

import {
  Avatar,
  Badge,
  Button,
  ListHeader,
  ListHeaderItem,
  ListRow,
  ListRowItem,
  ListRows,
  ListSelectBanner,
  ListView,
} from 'frappe-ui'

const custom_columns = reactive([
  { label: 'Name', key: 'name', width: 3, icon: 'lucide-user' },
  { label: 'Email', key: 'email', width: '200px', icon: 'lucide-at-sign' },
  { label: 'Role', key: 'role', icon: 'lucide-users' },
  { label: 'Status', key: 'status', icon: 'lucide-check-circle' },
])

const custom_rows = [
  {
    id: 1,
    name: {
      label: 'John Doe',
      image: 'https://avatars.githubusercontent.com/u/499550',
    },
    email: 'john@doe.com',
    status: { label: 'Active', bg_color: 'bg-surface-green-3' },
    role: { label: 'Developer', color: 'green' },
  },
  {
    id: 2,
    name: {
      label: 'Jane Doe',
      image: 'https://avatars.githubusercontent.com/u/499120',
    },
    email: 'jane@doe.com',
    status: { label: 'Inactive', bg_color: 'bg-surface-red-5' },
    role: { label: 'HR', color: 'red' },
  },
]
</script>

<template>
  <ListView
    class="h-[150px]"
    :columns="custom_columns"
    :rows="custom_rows"
    :options="{
      onRowClick: (row) => console.log(row),
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
    }"
    row-key="id"
  >
    <ListHeader>
      <ListHeaderItem
        v-for="column in custom_columns"
        :key="column.key"
        :item="column"
      >
        <template #prefix="{ item }">
          <span class="size-4" :class="item.icon" />
        </template>
      </ListHeaderItem>
    </ListHeader>
    <ListRows>
      <ListRow
        v-for="row in custom_rows"
        :key="row.id"
        v-slot="{ column, item }"
        :row="row"
      >
        <ListRowItem :item="item" :align="column.align">
          <template #prefix>
            <div
              v-if="column.key === 'status'"
              class="h-3 w-3 rounded-full"
              :class="item.bg_color"
            />
            <Avatar
              v-if="column.key === 'name'"
              shape="circle"
              :image="item.image"
              size="sm"
            />
          </template>
          <Badge
            v-if="column.key === 'role'"
            variant="subtle"
            :theme="item.color"
            size="md"
            :label="item.label"
          />
        </ListRowItem>
      </ListRow>
    </ListRows>
    <ListSelectBanner>
      <template #actions="{ unselectAll }">
        <div class="flex gap-2">
          <Button variant="ghost" label="Delete" />
          <Button variant="ghost" label="Unselect all" @click="unselectAll" />
        </div>
      </template>
    </ListSelectBanner>
  </ListView>
</template>

Grouped Rows

Name
Email
Role
Status
Developer (3)
Manager (2)
Designer (2)
HR (3)
Tester (2)
vue
<script setup>
import { reactive, ref } from 'vue'
import { ListView } from 'frappe-ui'

const group_columns = reactive([
  { label: 'Name', key: 'name', width: 3 },
  { label: 'Email', key: 'email', width: '200px' },
  { label: 'Role', key: 'role' },
  { label: 'Status', key: 'status' },
])

const grouped_rows = ref([
  {
    group: 'Developer',
    collapsed: false,
    rows: [
      {
        id: 2,
        name: 'Gary Fox',
        email: 'gary@fox.com',
        status: 'Inactive',
        role: 'Developer',
      },
      {
        id: 6,
        name: 'Emily Davis',
        email: 'emily@davis.com',
        status: 'Active',
        role: 'Developer',
      },
      {
        id: 9,
        name: 'David Lee',
        email: 'david@lee.com',
        status: 'Inactive',
        role: 'Developer',
      },
    ],
  },
  {
    group: 'Manager',
    collapsed: false,
    rows: [
      {
        id: 3,
        name: 'John Doe',
        email: 'john@doe.com',
        status: 'Active',
        role: 'Manager',
      },
      {
        id: 8,
        name: 'Sarah Wilson',
        email: 'sarah@wilson.com',
        status: 'Active',
        role: 'Manager',
      },
    ],
  },
  {
    group: 'Designer',
    collapsed: false,
    rows: [
      {
        id: 4,
        name: 'Alice Smith',
        email: 'alice@smith.com',
        status: 'Active',
        role: 'Designer',
      },
      {
        id: 10,
        name: 'Olivia Taylor',
        email: 'olivia@taylor.com',
        status: 'Active',
        role: 'Designer',
      },
    ],
  },
  {
    group: 'HR',
    collapsed: false,
    rows: [
      {
        id: 1,
        name: 'Jane Mary',
        email: 'jane@doe.com',
        status: 'Inactive',
        role: 'HR',
      },
      {
        id: 7,
        name: 'Michael Brown',
        email: 'michael@brown.com',
        status: 'Inactive',
        role: 'HR',
      },
      {
        id: 12,
        name: 'Sophia Martinez',
        email: 'sophia@martinez.com',
        status: 'Active',
        role: 'HR',
      },
    ],
  },
  {
    group: 'Tester',
    collapsed: false,
    rows: [
      {
        id: 5,
        name: 'Bob Johnson',
        email: 'bob@johnson.com',
        status: 'Inactive',
        role: 'Tester',
      },
      {
        id: 11,
        name: 'James Anderson',
        email: 'james@anderson.com',
        status: 'Inactive',
        role: 'Tester',
      },
    ],
  },
])
</script>

<template>
  <ListView
    class="h-[250px]"
    :columns="group_columns"
    :rows="grouped_rows"
    :options="{
      getRowRoute: (row) => ({
        name: 'User',
        params: { userId: row.id },
      }),
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
    }"
    row-key="id"
  >
    <template #group-header="{ group }">
      <span class="text-base font-medium leading-6 text-ink-gray-9">
        {{ group.group }} ({{ group.rows.length }})
      </span>
    </template>
  </ListView>
</template>

Cell Slot

Name
Email
Role
Status
vue
<script setup>
import { h, reactive } from 'vue'
import { Avatar, Badge, ListView } from 'frappe-ui'

const cols = reactive([
  {
    label: 'Name',
    key: 'name',
    width: 3,
    getLabel: ({ row }) => row.name,
    prefix: ({ row }) =>
      h(Avatar, { shape: 'circle', image: row.user_image, size: 'sm' }),
  },
  { label: 'Email', key: 'email', width: '200px' },
  { label: 'Role', key: 'role' },
  { label: 'Status', key: 'status' },
])

const rows = [
  {
    id: 1,
    name: 'John Doe',
    email: 'john@doe.com',
    status: 'Active',
    role: 'Developer',
    user_image: 'https://avatars.githubusercontent.com/u/499550',
  },
  {
    id: 2,
    name: 'Jane Doe',
    email: 'jane@doe.com',
    status: 'Inactive',
    role: 'HR',
    user_image: 'https://avatars.githubusercontent.com/u/499120',
  },
]
</script>

<template>
  <ListView
    class="h-[250px]"
    :columns="cols"
    :rows="rows"
    :options="{
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
      emptyState: {
        title: 'No records found',
        description: 'Create a new record to get started',
        button: {
          label: 'New Record',
          variant: 'solid',
          onClick: () => console.log('New Record'),
        },
      },
    }"
    row-key="id"
  >
    <template #cell="{ item, row, column }">
      <Badge v-if="column.key === 'status'">{{ item }}</Badge>
      <span class="font-medium text-ink-gray-7" v-else>{{ item }}</span>
    </template>
  </ListView>
</template>

Empty List

Name
Email
Role
Status
No records found
Create a new record to get started
vue
<script setup>
import { h, reactive } from 'vue'
import { Avatar, ListView } from 'frappe-ui'

const cols = reactive([
  {
    label: 'Name',
    key: 'name',
    width: 3,
    getLabel: ({ row }) => row.name,
    prefix: ({ row }) =>
      h(Avatar, { shape: 'circle', image: row.user_image, size: 'sm' }),
  },
  { label: 'Email', key: 'email', width: '200px' },
  { label: 'Role', key: 'role' },
  { label: 'Status', key: 'status' },
])
</script>

<template>
  <ListView
    class="h-[250px]"
    :columns="cols"
    :rows="[]"
    :options="{
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
      emptyState: {
        title: 'No records found',
        description: 'Create a new record to get started',
        button: {
          label: 'New Record',
          variant: 'solid',
          onClick: () => console.log('New Record'),
        },
      },
    }"
  />
</template>

Disabled Rows

Name
Email
Role
Status
vue
<script setup>
import { h, reactive } from 'vue'

import { Avatar, ListView } from 'frappe-ui'

const columns = reactive([
  {
    label: 'Name',
    key: 'name',
    width: 3,
    getLabel: ({ row }) => row.name,
    prefix: ({ row }) =>
      h(Avatar, { shape: 'circle', image: row.user_image, size: 'sm' }),
  },
  { label: 'Email', key: 'email', width: '200px' },
  { label: 'Role', key: 'role' },
  { label: 'Status', key: 'status' },
])

const rows = [
  {
    id: 1,
    disabled: true,
    name: 'John Doe',
    email: 'john@doe.com',
    status: 'Active',
    role: 'Developer',
    user_image: 'https://avatars.githubusercontent.com/u/499550',
  },
  {
    id: 2,
    name: 'Jane Doe',
    email: 'jane@doe.com',
    status: 'Inactive',
    role: 'HR',
    user_image: 'https://avatars.githubusercontent.com/u/499120',
  },
]
</script>

<template>
  <ListView
    class="h-[150px]"
    :columns="columns"
    :rows="rows"
    :options="{
      getRowRoute: (row) => ({
        name: 'User',
        params: { userId: row.id },
      }),
      selectable: true,
      showTooltip: true,
      resizeColumn: true,
    }"
    row-key="id"
  />
</template>