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

For controls that submit an application-owned page change, use real buttons and disabled. For link pagination, omit href or render non-actionable text at an unavailable boundary; pointer-events styling alone is not a disabled behavior.

Join Composition

Use the independent Join primitive when pagination should read as one connected control. This is additive and does not replace .pagination:

Pagination composed with Join

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

Class Description
.pagination Base pagination container (required)
.pagination-outlined Outlined variant with borders
.pagination-tonal Tonal variant with container colors
.pagination-sm Small size variant
.pagination-lg Large size variant
.pagination-compact Minimal spacing between items
.pagination-info Container with page info display
.pagination-responsive Wrapping layout for mobile

Item Classes

Class Description
.pagination-item Individual page button/link
.pagination-item-active Active page (primary color)
.pagination-item-active-primary Active page with primary color
.pagination-item-active-secondary Active page with secondary color
.pagination-item-active-tertiary Active page with tertiary color
.pagination-item-disabled Disabled page item
Class Description
.pagination-prev Previous page button
.pagination-next Next page button
.pagination-ellipsis Ellipsis indicator

Utility Classes

Class Description
.pagination-info-text Page information text
.pagination-input Page 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