Skip to main content

Overview

The Pagination component (mt-pagination) provides navigation controls for paginated content. It includes first page, previous page, next page, and last page buttons, along with a direct page number input and an information display showing the current range of items.

Import

import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

Props

currentPage
number
required
The currently active page number (1-indexed)
limit
number
required
Number of items to display per page
totalItems
number
required
Total number of items across all pages

Events

change-current-page
(pageNumber: number) => void
Emitted when the user navigates to a different page. Returns the new page number

Usage Examples

Basic Pagination

<template>
  <div>
    <div class="items-list">
      <div v-for="item in paginatedItems" :key="item.id">
        {{ item.name }}
      </div>
    </div>
    
    <mt-pagination
      :current-page="currentPage"
      :limit="itemsPerPage"
      :total-items="totalItems"
      @change-current-page="handlePageChange"
    />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const currentPage = ref(1);
const itemsPerPage = ref(20);
const totalItems = ref(150);

const allItems = ref([
  // Your items array
]);

const paginatedItems = computed(() => {
  const start = (currentPage.value - 1) * itemsPerPage.value;
  const end = start + itemsPerPage.value;
  return allItems.value.slice(start, end);
});

const handlePageChange = (page) => {
  currentPage.value = page;
  // Optional: Scroll to top
  window.scrollTo({ top: 0, behavior: 'smooth' });
};
</script>

Pagination with API Data

<template>
  <div>
    <div v-if="loading">Loading...</div>
    
    <div v-else class="data-grid">
      <div v-for="item in items" :key="item.id">
        {{ item.title }}
      </div>
    </div>
    
    <mt-pagination
      :current-page="pagination.page"
      :limit="pagination.limit"
      :total-items="pagination.total"
      @change-current-page="loadPage"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const items = ref([]);
const loading = ref(false);

const pagination = ref({
  page: 1,
  limit: 25,
  total: 0
});

const loadPage = async (page) => {
  loading.value = true;
  
  try {
    const response = await fetch(
      `/api/items?page=${page}&limit=${pagination.value.limit}`
    );
    const data = await response.json();
    
    items.value = data.items;
    pagination.value.page = page;
    pagination.value.total = data.total;
  } catch (error) {
    console.error('Failed to load page:', error);
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  loadPage(1);
});
</script>

Pagination with Variable Page Size

<template>
  <div>
    <div class="controls">
      <label>
        Items per page:
        <select v-model.number="pageSize" @change="handlePageSizeChange">
          <option :value="10">10</option>
          <option :value="25">25</option>
          <option :value="50">50</option>
          <option :value="100">100</option>
        </select>
      </label>
    </div>
    
    <div class="items">
      <!-- Your items -->
    </div>
    
    <mt-pagination
      :current-page="currentPage"
      :limit="pageSize"
      :total-items="totalItems"
      @change-current-page="currentPage = $event"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const currentPage = ref(1);
const pageSize = ref(25);
const totalItems = ref(500);

const handlePageSizeChange = () => {
  // Reset to first page when changing page size
  currentPage.value = 1;
};
</script>

Pagination in a Data Table

<template>
  <mt-card title="Customer List">
    <table class="data-table">
      <thead>
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th>Status</th>
          <th>Created</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="customer in paginatedCustomers" :key="customer.id">
          <td>{{ customer.name }}</td>
          <td>{{ customer.email }}</td>
          <td>{{ customer.status }}</td>
          <td>{{ formatDate(customer.createdAt) }}</td>
        </tr>
      </tbody>
    </table>
    
    <template #footer>
      <mt-pagination
        :current-page="currentPage"
        :limit="limit"
        :total-items="customers.length"
        @change-current-page="currentPage = $event"
      />
    </template>
  </mt-card>
</template>

<script setup>
import { ref, computed } from 'vue';
import MtCard from '@/components/layout/mt-card/mt-card.vue';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const currentPage = ref(1);
const limit = ref(20);
const customers = ref([/* customer data */]);

const paginatedCustomers = computed(() => {
  const start = (currentPage.value - 1) * limit.value;
  return customers.value.slice(start, start + limit.value);
});

const formatDate = (date) => {
  return new Date(date).toLocaleDateString();
};
</script>

Pagination with Search/Filter

<template>
  <div>
    <div class="toolbar">
      <input
        v-model="searchQuery"
        type="search"
        placeholder="Search products..."
        @input="handleSearch"
      />
    </div>
    
    <div class="products">
      <div v-for="product in displayedProducts" :key="product.id">
        {{ product.name }}
      </div>
    </div>
    
    <mt-pagination
      :current-page="currentPage"
      :limit="limit"
      :total-items="filteredProducts.length"
      @change-current-page="currentPage = $event"
    />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const searchQuery = ref('');
const currentPage = ref(1);
const limit = ref(30);

const allProducts = ref([
  // All products
]);

const filteredProducts = computed(() => {
  if (!searchQuery.value) return allProducts.value;
  
  return allProducts.value.filter(product =>
    product.name.toLowerCase().includes(searchQuery.value.toLowerCase())
  );
});

const displayedProducts = computed(() => {
  const start = (currentPage.value - 1) * limit.value;
  return filteredProducts.value.slice(start, start + limit.value);
});

const handleSearch = () => {
  // Reset to first page when searching
  currentPage.value = 1;
};
</script>

Pagination with URL Sync

<template>
  <div>
    <div class="content">
      <!-- Your paginated content -->
    </div>
    
    <mt-pagination
      :current-page="currentPage"
      :limit="limit"
      :total-items="totalItems"
      @change-current-page="handlePageChange"
    />
  </div>
</template>

<script setup>
import { ref, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import MtPagination from '@/components/table-and-list/mt-pagination/mt-pagination.vue';

const route = useRoute();
const router = useRouter();

const currentPage = ref(1);
const limit = ref(20);
const totalItems = ref(1000);

const handlePageChange = (page) => {
  currentPage.value = page;
  router.push({
    query: { ...route.query, page }
  });
};

// Initialize from URL on mount
onMounted(() => {
  const pageFromUrl = parseInt(route.query.page as string);
  if (pageFromUrl && pageFromUrl > 0) {
    currentPage.value = pageFromUrl;
  }
});
</script>

Features

  • First Page: Jump to the first page
  • Previous Page: Go to the previous page
  • Next Page: Go to the next page
  • Last Page: Jump to the last page

Direct Page Input

Users can type a page number directly into the input field to jump to a specific page.

Information Display

Shows the current range of items being displayed (e.g., “1-20 of 150”).

Automatic Page Reset

When the limit prop changes, the component automatically resets to page 1.

Accessibility

  • All buttons include visually hidden text for screen readers
  • Icons have aria-hidden="true" to prevent duplicate announcements
  • Buttons are properly disabled when at boundaries (first/last page)
  • Page input includes an accessible label

Calculated Behavior

  • Total pages: Math.ceil(totalItems / limit)
  • First visible item: (currentPage - 1) * limit + 1
  • Last visible item: Math.min(limit * currentPage, totalItems)
  • First page button disabled when currentPage === 1
  • Last page button disabled when currentPage === totalPages

Source

View the component source at /home/daytona/workspace/source/packages/component-library/src/components/table-and-list/mt-pagination/mt-pagination.vue:1

Build docs developers (and LLMs) love