Pagination

Material Design 3 pagination component for navigating through pages of content

Pagination

Pagination allows users to navigate through pages of content. @duskmoon-dev/core provides a complete set of Material Design 3 pagination styles with multiple variants and configurations.

Basic Usage

Basic Pagination

Variants

Default Variant

The default pagination uses circular buttons with subtle hover effects:

Default Variant with Icons

Outlined Variant

Outlined pagination adds borders to page items:

Outlined Variant

Tonal Variant

Tonal pagination uses container colors for active states:

Tonal Variant

Active Page Colors

Primary (Default)

Primary Color

Secondary

Secondary Color

Tertiary

Tertiary Color

Sizes

Small

Small Size

Default (Medium)

Medium Size

Large

Large Size

Spacing

Default Spacing

Default Spacing

Compact

Minimal spacing between items:

Compact Spacing

Ellipsis

Use ellipsis to indicate more pages:

Pagination with Ellipsis

Disabled State

Disable navigation buttons when at boundaries:

Disabled Previous Button

You can also disable individual page items:

Disabled Page Item

With Page Info

Display current page information:

Pagination with Page Info

Page 2 of 10

With Page Jump

Allow users to jump to a specific page:

Pagination with Page Jump

Responsive

Pagination that wraps on smaller screens:

Responsive Pagination

Advanced Examples

Complete Pagination

Combining multiple features:

Complete Pagination

Showing 21-30 of 200 items

Minimal Pagination

Simple previous/next navigation:

Minimal Pagination

Best Practices

Visual Feedback

Always provide clear visual feedback for the current page:

Visual Feedback Example

Appropriate Page Ranges

Show a reasonable number of pages (typically 5-7) to avoid overwhelming users:

Appropriate Page Range

Accessibility

Ensure pagination is accessible:

Accessible Pagination

Mobile Considerations

Use compact or responsive variants on mobile devices:

Mobile-Friendly Pagination

Framework Examples

React

interface PaginationProps {
  currentPage: number;
  totalPages: number;
  onPageChange: (page: number) => void;
  variant?: 'default' | 'outlined' | 'tonal';
  size?: 'sm' | 'md' | 'lg';
  color?: 'primary' | 'secondary' | 'tertiary';
}

export function Pagination({
  currentPage,
  totalPages,
  onPageChange,
  variant = 'default',
  size = 'md',
  color = 'primary'
}: PaginationProps) {
  const pages = [];
  const maxVisible = 5;

  let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2));
  let endPage = Math.min(totalPages, startPage + maxVisible - 1);

  if (endPage - startPage < maxVisible - 1) {
    startPage = Math.max(1, endPage - maxVisible + 1);
  }

  for (let i = startPage; i <= endPage; i++) {
    pages.push(i);
  }

  const variantClass = variant !== 'default' ? `pagination-${variant}` : '';
  const sizeClass = size !== 'md' ? `pagination-${size}` : '';
  const colorClass = color !== 'primary' ? `pagination-item-active-${color}` : 'pagination-item-active';

  return (
    <nav className={`pagination ${variantClass} ${sizeClass}`.trim()}>
      <button
        className="pagination-prev"
        onClick={() => onPageChange(currentPage - 1)}
        disabled={currentPage === 1}
      >
        Previous
      </button>

      {startPage > 1 && (
        <>
          <a href="#" className="pagination-item" onClick={(e) => { e.preventDefault(); onPageChange(1); }}>
            1
          </a>
          {startPage > 2 && <span className="pagination-ellipsis"></span>}
        </>
      )}

      {pages.map((page) => (
        <a
          key={page}
          href="#"
          className={`pagination-item ${page === currentPage ? colorClass : ''}`}
          onClick={(e) => { e.preventDefault(); onPageChange(page); }}
        >
          {page}
        </a>
      ))}

      {endPage < totalPages && (
        <>
          {endPage < totalPages - 1 && <span className="pagination-ellipsis"></span>}
          <a href="#" className="pagination-item" onClick={(e) => { e.preventDefault(); onPageChange(totalPages); }}>
            {totalPages}
          </a>
        </>
      )}

      <button
        className="pagination-next"
        onClick={() => onPageChange(currentPage + 1)}
        disabled={currentPage === totalPages}
      >
        Next
      </button>
    </nav>
  );
}

// Usage
<Pagination
  currentPage={2}
  totalPages={10}
  onPageChange={(page) => console.log('Go to page', page)}
  variant="outlined"
  color="primary"
/>

Vue

<template>
  <nav :class="paginationClasses">
    <button
      class="pagination-prev"
      @click="$emit('update:currentPage', currentPage - 1)"
      :disabled="currentPage === 1"
    >
      Previous
    </button>

    <a
      v-if="startPage > 1"
      href="#"
      class="pagination-item"
      @click.prevent="$emit('update:currentPage', 1)"
    >
      1
    </a>
    <span v-if="startPage > 2" class="pagination-ellipsis"></span>

    <a
      v-for="page in visiblePages"
      :key="page"
      href="#"
      :class="['pagination-item', page === currentPage ? activeClass : '']"
      @click.prevent="$emit('update:currentPage', page)"
    >
      {{ page }}
    </a>

    <span v-if="endPage < totalPages - 1" class="pagination-ellipsis"></span>
    <a
      v-if="endPage < totalPages"
      href="#"
      class="pagination-item"
      @click.prevent="$emit('update:currentPage', totalPages)"
    >
      {{ totalPages }}
    </a>

    <button
      class="pagination-next"
      @click="$emit('update:currentPage', currentPage + 1)"
      :disabled="currentPage === totalPages"
    >
      Next
    </button>
  </nav>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  currentPage: {
    type: Number,
    required: true
  },
  totalPages: {
    type: Number,
    required: true
  },
  variant: {
    type: String,
    default: 'default'
  },
  size: {
    type: String,
    default: 'md'
  },
  color: {
    type: String,
    default: 'primary'
  },
  maxVisible: {
    type: Number,
    default: 5
  }
});

defineEmits(['update:currentPage']);

const paginationClasses = computed(() => {
  const classes = ['pagination'];
  if (props.variant !== 'default') classes.push(`pagination-${props.variant}`);
  if (props.size !== 'md') classes.push(`pagination-${props.size}`);
  return classes.join(' ');
});

const activeClass = computed(() => {
  return props.color !== 'primary'
    ? `pagination-item-active-${props.color}`
    : 'pagination-item-active';
});

const startPage = computed(() => {
  let start = Math.max(1, props.currentPage - Math.floor(props.maxVisible / 2));
  const end = Math.min(props.totalPages, start + props.maxVisible - 1);
  if (end - start < props.maxVisible - 1) {
    start = Math.max(1, end - props.maxVisible + 1);
  }
  return start;
});

const endPage = computed(() => {
  return Math.min(props.totalPages, startPage.value + props.maxVisible - 1);
});

const visiblePages = computed(() => {
  const pages = [];
  for (let i = startPage.value; i <= endPage.value; i++) {
    pages.push(i);
  }
  return pages;
});
</script>

<!-- Usage -->
<Pagination
  v-model:current-page="currentPage"
  :total-pages="10"
  variant="outlined"
  color="primary"
/>

API Reference

Container Classes

ClassDescription
.paginationBase pagination container (required)
.pagination-outlinedOutlined variant with borders
.pagination-tonalTonal variant with container colors
.pagination-smSmall size variant
.pagination-lgLarge size variant
.pagination-compactMinimal spacing between items
.pagination-infoContainer with page info display
.pagination-responsiveWrapping layout for mobile

Item Classes

ClassDescription
.pagination-itemIndividual page button/link
.pagination-item-activeActive page (primary color)
.pagination-item-active-primaryActive page with primary color
.pagination-item-active-secondaryActive page with secondary color
.pagination-item-active-tertiaryActive page with tertiary color
.pagination-item-disabledDisabled page item
ClassDescription
.pagination-prevPrevious page button
.pagination-nextNext page button
.pagination-ellipsisEllipsis indicator

Utility Classes

ClassDescription
.pagination-info-textPage information text
.pagination-inputPage jump input container

Combinations

You can combine classes for different effects:

Outlined Large with Secondary Color

  • Table - Tabular data display
  • List - Ordered and unordered lists
  • Button - Navigation buttons

See Also