Skip to content

Creating Your Own Toolbar



Vue PDF Viewer’s default toolbar provides convenient, out-of-the-box controls for navigating and manipulating PDF documents. However, there may be cases where you need a custom UI or prefer a more minimal or specialized set of tools.

In this tutorial, you will learn how to replace Vue PDF Viewer’s default toolbar with a fully customized set of controls using the Instance API with the following steps:

  1. Disable the default toolbar
  2. Build your own toolbar with:
    • Navigation controls
    • Zoom functionality
    • Download capabilities
    • Print controls
  3. Combine everything into a completely new toolbar

Overview of Custom Toolbar

In the example below, we will be creating a toolbar with basic functions such as zoom, page navigation file download and print. Image of a Vue PDF Viewer Toolbar

Disabling the Default Toolbar

Before creating a custom toolbar, you will need to remove the built-in toolbar provided by Vue PDF Viewer. This can be done by setting the :toolbar-options prop to false.

vue
<VPdfViewer :toolbar-options="false" />

With the toolbar disabled, the Viewer will no longer display the default controls, giving you a clean slate to build your own.

Image of a Vue PDF Viewer without Toolbar

Time to Building Your Own Toolbar

Now that the default toolbar is disabled, we will create a fully customized toolbar tailored to your application's design and functionality.

Our toolbar includes:

  • Navigation controls – Buttons for navigating pages
  • Zoom controls – Buttons for increasing and decreasing zoom levels
  • Download option – A button to download the PDF
  • Print feature – A button to print the PDF

To access the API, we will use Vue.js ref to create a reference to the Viewer instance.

vue
<script lang="ts" setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer';
    
const vpvRef = ref<InstanceType<typeof VPdfViewer>>();
</script>

<template>
  <VPdfViewer ref="vpvRef" />
</template>
vue
<script lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer';
    
const vpvRef = ref(null);
</script>

<template>
  <VPdfViewer ref="vpvRef" />
</template>
vue
<script lang="ts">
import { VPdfViewer } from "@vue-pdf-viewer/viewer";
    
export default {
  components: {
    VPdfViewer,
  },
    
  data() {
    return {
      vpvRef: null,
    }
  },
}
</script>

<template>
  <VPdfViewer ref="vpvRef" />
</template>
vue
<script>
import { VPdfViewer } from "@vue-pdf-viewer/viewer";
    
export default {
  components: {
    VPdfViewer,
  },
    
  data() {
    return {
      vpvRef: null,
    }
  },
}
</script>

<template>
  <VPdfViewer ref="vpvRef" />
</template>

Next, we will start creating the toolbar component by setting up the basic structure.

For styling of the toolbar, Tailwind CSS and Font Awesome icons will be used.

INFO

To use Font Awesome icons, you will need to include Font Awesome in your project. For simplicity, we will use a CDN in our example to make it easy to follow along.

vue
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">

In the sections below, we will walkthrough by adding each function separately before combining the codes at the end.

Zoom-in & Zoom-out Function

Vue PDF Viewer provides a Zoom Controller as part of its Instance API, allowing you to implement custom zoom controls within your application.

We will use Vue's ref and computed functions to track and adjust the current zoom level dynamically.

In this section, we will implement:

  • Zoom In – Increase the zoom level by 0.25
  • Zoom Out – Decrease the zoom level by 0.25

Image of zoomout and zoomin in toolbar

The code snippet below demonstrates how to integrate these custom zoom controls into your toolbar.

vue
<script setup lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
    
const vpvRef = ref<InstanceType<typeof VPdfViewer>>()

// Access zoom controls
const zoomControl = computed(() => vpvRef.value?.zoomControl);
    
// Track current zoom level
const currentScale = computed(() => {
  return zoomControl.value?.scale;
});

// Handle different zoom actions    
const handleZoomTool = (type: "in" | "out") => {
  const zoomCtrl = unref(zoomControl);
  if (!zoomCtrl) return;
  const scale = unref(currentScale);
  if (type === "in") {
    scale && zoomCtrl.zoom(scale + 0.25); // Zoom in by 0.25
  } else if (type === "out") {
    scale && zoomCtrl.zoom(scale - 0.25); // Zoom out by 0.25
  } else {
    zoomCtrl.zoom(type as ZoomLevel); // Set to specific zoom level
  }
};
</script>
vue
<script setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, unref } from 'vue'

const vpvRef = ref(null)

const zoomControl = computed(() => vpvRef.value?.zoomControl)

const currentScale = computed(() => {
  return zoomControl.value?.scale
})
  
const handleZoomTool = (type) => {
  const zoomCtrl = unref(zoomControl)
  if (!zoomCtrl) return
  
  const scale = unref(currentScale)
  if (type === "in") {
    scale && zoomCtrl.zoom(scale + 0.25)
  } else if (type === "out") {
    scale && zoomCtrl.zoom(scale - 0.25)
  } else {
    zoomCtrl.zoom(type)
  }
}
</script>
vue
<script lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref<InstanceType<typeof VPdfViewer>>()
    
    const zoomControl = computed(() => vpvRef.value?.zoomControl)
    const currentScale = computed(() => zoomControl.value?.scale)
    
    const handleZoomTool = (type: 'in' | 'out') => {
      const zoomCtrl = unref(zoomControl)
      if (!zoomCtrl) return
      
      const scale = unref(currentScale)
      if (type === 'in') {
        scale && zoomCtrl.zoom(scale + 0.25)
      } else if (type === 'out') {
        scale && zoomCtrl.zoom(scale - 0.25)
      } else {
        zoomCtrl.zoom(type)
      }
    }

    return {
      vpvRef,
      handleZoomTool,
      zoomControl,
      currentScale
    }
  }
})
</script>
vue
<script>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const zoomControl = computed(() => vpvRef.value?.zoomControl)
    const currentScale = computed(() => zoomControl.value?.scale)
    
    const handleZoomTool = (type) => {
      const zoomCtrl = unref(zoomControl)
      if (!zoomCtrl) return
      
      const scale = unref(currentScale)
      if (type === 'in') {
        scale && zoomCtrl.zoom(scale + 0.25)
      } else if (type === 'out') {
        scale && zoomCtrl.zoom(scale - 0.25)
      } else {
        zoomCtrl.zoom(type)
      }
    }

    return {
      vpvRef,
      handleZoomTool,
      zoomControl,
      currentScale
    }
  }
})
</script>
vue
<template>
  <div class="py-2 px-2">
    <div class="flex flex-col gap-4 justify-self-center">
      <div class="flex items-center gap-4 text-[#7862FF] bg-pale-blue border-[#D7D1FB] rounded-lg p-2 justify-center">
      
        <!-- Zoom out button -->
        <button @click="() => handleZoomTool('out')">
          <i class="fa-solid fa-magnifying-glass-minus"></i>
        </button>
                
        <!-- Zoom in button -->
        <button @click="() => handleZoomTool('in')">
          <i class="fa-solid fa-magnifying-glass-plus"></i>
        </button>
      </div>
    </div>
  </div>
</template>

Page Control Function

Vue PDF Viewer provides a Page Controller via its Instance API, allowing you to programmatically control page navigation and track the current position.

In this section, we will implement:

  • Previous Page – Navigate to the previous page unless already on the first page.
  • Next Page – Navigate to the next page unless already on the last page.
  • Page Input – Allow users to enter a page number and navigate directly to that page.

Image of zoomout zoomin pagecontrol in toolbar

The following code snippet demonstrates how to integrate custom page navigation controls into your toolbar.

vue
<script setup lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'

const vpvRef = ref<InstanceType<typeof VPdfViewer>>();

// Computed properties to access various controls from the viewer
const pageControl = computed(() => vpvRef.value?.pageControl);
const currentPageInput = computed(() => pageControl.value?.currentPage);
const searchControl = computed(() => vpvRef.value?.searchControl);

// Get the total number of search matches when searching in the PDF
const totalMatches = computed(
  () => searchControl?.value?.searchMatches?.totalMatches,
);

// Function to navigate to the previous page
const prevPage = () => {
  // Check if we're on the first page
  const isFirstPage = pageControl.value?.currentPage === 1;
  if (isFirstPage) {
    return; // Don't do anything if we're already on first page
  }
  // Navigate to previous page
  pageControl.value?.goToPage(pageControl.value?.currentPage - 1);
};
    
// Function to navigate to the next page
const nextPage = () => {
  // Check if we're on the last page
  const isLastPage =
    pageControl.value?.currentPage === pageControl.value?.totalPages;
  if (isLastPage) {
    return; // Don't do anything if we're already on last page
  }
  // Navigate to next page
  pageControl.value?.goToPage(pageControl.value?.currentPage + 1);
};
    
// Handle Enter key press in the page input field
const handleKeyPress = (event: KeyboardEvent) => {
  if (event.key === "Enter") {
    handlePageInput(event);
  }
};
    
// Computed properties to control button disabled states
const isNextPageButtonDisable = computed(
  () => pageControl.value?.currentPage === pageControl.value?.totalPages,
);
const isPreviousPageButtonDisable = computed(
  () => pageControl.value?.currentPage === 1,
);
</script>
vue
<script setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed } from 'vue'

const vpvRef = ref(null)

const pageControl = computed(() => vpvRef.value?.pageControl)
const currentPageInput = computed(() => pageControl.value?.currentPage)
const searchControl = computed(() => vpvRef.value?.searchControl)
const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)

const isNextPageButtonDisable = computed(() => 
  pageControl.value?.currentPage === pageControl.value?.totalPages
)

const isPreviousPageButtonDisable = computed(() => 
  pageControl.value?.currentPage === 1
)

const prevPage = () => {
  const isFirstPage = pageControl.value?.currentPage === 1
  if (isFirstPage) return
  pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
}

const nextPage = () => {
  const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
  if (isLastPage) return
  pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
}

const handleKeyPress = (event) => {
  if (event.key === "Enter") {
    handlePageInput(event)
  }
}
</script>
vue
<script lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref<InstanceType<typeof VPdfViewer>>()
    
    const pageControl = computed(() => vpvRef.value?.pageControl)
    const currentPageInput = computed(() => pageControl.value?.currentPage)
    const searchControl = computed(() => vpvRef.value?.searchControl)
    const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
    
    const isNextPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === pageControl.value?.totalPages
    )
    
    const isPreviousPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === 1
    )

    const prevPage = () => {
      const isFirstPage = pageControl.value?.currentPage === 1
      if (isFirstPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
    }

    const nextPage = () => {
      const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
      if (isLastPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
    }

    const handleKeyPress = (event: KeyboardEvent) => {
      if (event.key === "Enter") {
        handlePageInput(event)
      }
    }

    return {
      vpvRef,
      pageControl,
      currentPageInput,
      searchControl,
      totalMatches,
      prevPage,
      nextPage,
      handleKeyPress,
      isNextPageButtonDisable,
      isPreviousPageButtonDisable
    }
  }
})
</script>
vue
<script>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const pageControl = computed(() => vpvRef.value?.pageControl)
    const currentPageInput = computed(() => pageControl.value?.currentPage)
    const searchControl = computed(() => vpvRef.value?.searchControl)
    const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
    
    const isNextPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === pageControl.value?.totalPages
    )
    
    const isPreviousPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === 1
    )

    const prevPage = () => {
      const isFirstPage = pageControl.value?.currentPage === 1
      if (isFirstPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
    }

    const nextPage = () => {
      const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
      if (isLastPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
    }

    const handleKeyPress = (event) => {
      if (event.key === "Enter") {
        handlePageInput(event)
      }
    }

    return {
      vpvRef,
      pageControl,
      currentPageInput,
      searchControl,
      totalMatches,
      prevPage,
      nextPage,
      handleKeyPress,
      isNextPageButtonDisable,
      isPreviousPageButtonDisable
    }
  }
})
</script>
vue
<template>
  <div>
    <!-- The rest of your code -->
    <!-- Previous page button -->
    <button @click="prevPage" :disabled="isPreviousPageButtonDisable">
      <i class="fa-solid fa-chevron-up" />
    </button>
        
    <!-- Page number input and total pages display -->
    <div class="flex items-center text-sm font-normal">
      <input 
        v-model="currentPageInput" 
        class="w-12 h-8 rounded-sm focus:outline-none pl-2" 
        @keypress="handleKeyPress"
      />
        <span class="pl-1">
          /{{ pageControl?.totalPages }}
        </span>
    </div>
        
    <!-- Next page button -->
    <button @click="nextPage" :disabled="isNextPageButtonDisable">
      <i class="fa-solid fa-chevron-down" />
    </button>
  </div>
</template>

Vue PDF Viewer provides a Print Controller via its Instance API, allowing you to implement custom print functionality with progress tracking and error management.

In this section, we will implement:

  • Print Button – Initiates the print process with a visible progress indicator.
  • Cancel Print – Allows users to cancel an ongoing print operation.
  • Progress Tracking – Monitors and logs print progress, including loading percentage and loaded pages.
  • Error Handling – Detects and logs errors during printing, with automatic cancellation upon failure.
  • Success Handling – Notifies when the print job completes successfully.

Image of zoomout zoomin pagecontrol and print tool in toolbar

To dynamically manage event handlers, we use Vue’s watch function to observe changes in the printControl object.

The following code snippet shows how to integrate custom print controls into your toolbar.

vue
<script setup lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'

const vpvRef = ref<InstanceType<typeof VPdfViewer>>();

// Computed property to access print control from the PDF viewer
const printControl = computed(() => vpvRef.value?.printControl);

// Function to handle print initiation
const handlePrintTool = () => {
  // Get the print control instance using Vue's unref utility
  const printCtrl = unref(printControl);
  // Guard clause: exit if print control is not available
  if (!printCtrl) return;
  // Start printing with visible progress indicator
  printCtrl.print({ visibleDefaultProgress: true });
};

// Function to cancel ongoing print operation
const cancelPrint = () => {
  // Get the print control instance
  const printCtrl = unref(printControl);
  // Guard clause: exit if print control is not available
  if (!printCtrl) return;
  // Cancel the print operation
  printCtrl.cancel();
};

// Watch for changes in printControl to set up event handlers
watch(printControl, (printCtrl) => {
  // Guard clause: exit if print control is not available
  if (!printCtrl) return;

  // Progress handler to track printing status
  printCtrl.onProgress = (progress: any) => {
    console.log("Print progress", progress.percentage, progress.loadedPages);
  };

  // Error handler for print failures
  printCtrl.onError = (error: any) => {
    console.log("Print error", error);
    // Automatically cancel print job on error
    cancelPrint();
  };

  // Success handler when printing completes
  printCtrl.onComplete = () => {
    console.log("Print completed");
  };
});
</script>
vue
<script setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, unref, watch } from 'vue'

const vpvRef = ref(null)

const printControl = computed(() => vpvRef.value?.printControl)

const handlePrintTool = () => {
  const printCtrl = unref(printControl)
  if (!printCtrl) return
  printCtrl.print({ visibleDefaultProgress: true })
}

const cancelPrint = () => {
  const printCtrl = unref(printControl)
  if (!printCtrl) return
  printCtrl.cancel()
}

watch(printControl, (printCtrl) => {
  if (!printCtrl) return

  printCtrl.onProgress = (progress) => {
    console.log("Print progress", progress.percentage, progress.loadedPages)
  }

  printCtrl.onError = (error) => {
    console.log("Print error", error)
    cancelPrint()
  }

  printCtrl.onComplete = () => {
    console.log("Print completed")
  }
})
</script>
vue
<script lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref, watch } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref<InstanceType<typeof VPdfViewer>>()
    
    const printControl = computed(() => vpvRef.value?.printControl)

    const handlePrintTool = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.print({ visibleDefaultProgress: true })
    }

    const cancelPrint = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.cancel()
    }

    watch(printControl, (printCtrl) => {
      if (!printCtrl) return

      printCtrl.onProgress = (progress: any) => {
        console.log("Print progress", progress.percentage, progress.loadedPages)
      }

      printCtrl.onError = (error: any) => {
        console.log("Print error", error)
        cancelPrint()
      }

      printCtrl.onComplete = () => {
        console.log("Print completed")
      }
    })

    return {
      vpvRef,
      printControl,
      handlePrintTool,
      cancelPrint
    }
  }
})
</script>
vue
<script>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref, watch } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const printControl = computed(() => vpvRef.value?.printControl)

    const handlePrintTool = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.print({ visibleDefaultProgress: true })
    }

    const cancelPrint = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.cancel()
    }

    watch(printControl, (printCtrl) => {
      if (!printCtrl) return

      printCtrl.onProgress = (progress) => {
        console.log("Print progress", progress.percentage, progress.loadedPages)
      }

      printCtrl.onError = (error) => {
        console.log("Print error", error)
        cancelPrint()
      }

      printCtrl.onComplete = () => {
        console.log("Print completed")
      }
    })

    return {
      vpvRef,
      printControl,
      handlePrintTool,
      cancelPrint
    }
  }
})
</script>
vue
<template>
  <div>
    <!-- The rest of your code -->
    <!-- Print button -->
    <button @click="handlePrintTool">
      <i class="fa-solid fa-print" />
    </button>
  </div>
</template>

Download Function

Vue PDF Viewer provides a Download Controller as part of its Instance API, allowing you to create a custom download feature instead of using the default toolbar button.

In this section, we will implement:

  • Download Button – Initiates the PDF download when clicked.
  • Error Handling – Logs errors if the download process fails.
  • Success Handling – Confirms successful download completion.

Image of all tool in toolbar

Using Vue’s watch function, we dynamically attach event handlers to the downloadControl object when it becomes available.

The code snippet below shows how to integrate a custom download button into your toolbar.

vue
<script setup lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'

const vpvRef = ref<InstanceType<typeof VPdfViewer>>();

// Computed property to access download control from the PDF viewer
const downloadControl = computed(() => vpvRef.value?.downloadControl);

// Function to handle file download
const handleDownloadFile = () => {
  // Get the download control instance using Vue's unref utility
  const downloadCtrl = unref(downloadControl);
  // Guard clause: exit if download control is not available
  if (!downloadCtrl) return;
  // Trigger the download
  downloadCtrl.download();
};

// Watch for changes in downloadControl to set up event handlers
watch(downloadControl, (downloadCtrl) => {
  // Guard clause: exit if download control is not available
  if (!downloadCtrl) return;
  
  // Error handler for download failures
  downloadCtrl.onError = (error: any) => {
    console.log("Download error", error);
  };
  
  // Success handler when download completes
  downloadCtrl.onComplete = () => {
    console.log("Download completed");
  };
});
</script>
vue
<script setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, unref, watch } from 'vue'

const vpvRef = ref(null)
const downloadControl = computed(() => vpvRef.value?.downloadControl)

const handleDownloadFile = () => {
  const downloadCtrl = unref(downloadControl)
  if (!downloadCtrl) return
  downloadCtrl.download()
}

watch(downloadControl, (downloadCtrl) => {
  if (!downloadCtrl) return
  
  downloadCtrl.onError = (error) => {
    console.log("Download error", error)
  }
  
  downloadCtrl.onComplete = () => {
    console.log("Download completed")
  }
})
</script>
```vue [Options TS]
vue
<script lang="ts">
import { defineComponent, ref, computed, watch } from 'vue'
import { VPdfViewer } from '@vue-pdf-viewer/viewer'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const downloadControl = computed(() => {
      return vpvRef.value?.downloadControl
    })
    
    const handleDownloadFile = () => {
      const downloadCtrl = downloadControl.value
      if (!downloadCtrl) return
      downloadCtrl.download()
    }
    
    watch(downloadControl, (downloadCtrl) => {
      if (!downloadCtrl) return
      
      downloadCtrl.onError = (error: any) => {
        console.log("Download error", error)
      }
      
      downloadCtrl.onComplete = () => {
        console.log("Download completed")
      }
    })
    
    return {
      vpvRef,
      downloadControl,
      handleDownloadFile
    }
  }
})
</script>
vue
<script>
import { defineComponent, ref, computed, watch } from 'vue'
import { VPdfViewer } from '@vue-pdf-viewer/viewer'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const downloadControl = computed(() => {
      return vpvRef.value?.downloadControl
    })
    
    const handleDownloadFile = () => {
      const downloadCtrl = downloadControl.value
      if (!downloadCtrl) return
      downloadCtrl.download()
    }
    
    watch(downloadControl, (downloadCtrl) => {
      if (!downloadCtrl) return
      
      downloadCtrl.onError = (error) => {
        console.log("Download error", error)
      }
      
      downloadCtrl.onComplete = () => {
        console.log("Download completed")
      }
    })
    
    return {
      vpvRef,
      downloadControl,
      handleDownloadFile
    }
  }
})
</script>
vue
<template>
  <div>
    <!-- The rest of your code -->
    <!-- Download button -->
    <button @click="handleDownloadFile">
      <i class="fa-solid fa-download" />
    </button>
  </div>
</template>

Complete Example

Now that we have covered each feature step by step, let's assemble the complete custom toolbar in a single file.

Here is the complete example that includes custom toolbar controls for downloading, zooming, page navigation, and printing.

Image of a Vue PDF Viewer Toolbar

vue
<script setup lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
    
const vpvRef = ref<InstanceType<typeof VPdfViewer>>()

const zoomControl = computed(() => vpvRef.value?.zoomControl);
const currentScale = computed(() => {
  return zoomControl.value?.scale;
});
  
const handleZoomTool = (type: "in" | "out") => {
  const zoomCtrl = unref(zoomControl);
  if (!zoomCtrl) return;
  const scale = unref(currentScale);
  if (type === "in") {
    scale && zoomCtrl.zoom(scale + 0.25);
  } else if (type === "out") {
    scale && zoomCtrl.zoom(scale - 0.25); 
  } else {
    zoomCtrl.zoom(type as ZoomLevel);
  }
};

const pageControl = computed(() => vpvRef.value?.pageControl);
const currentPageInput = computed(() => pageControl.value?.currentPage);
const searchControl = computed(() => vpvRef.value?.searchControl);
const totalMatches = computed(
  () => searchControl?.value?.searchMatches?.totalMatches,
);
const prevPage = () => {
  const isFirstPage = pageControl.value?.currentPage === 1;
  if (isFirstPage) {
    return;
  }
    pageControl.value?.goToPage(pageControl.value?.currentPage - 1);
};

const prevPage = () => {
  const isFirstPage = pageControl.value?.currentPage === 1;
  if (isFirstPage) {
    return;
  }
    pageControl.value?.goToPage(pageControl.value?.currentPage - 1);
};
    
const nextPage = () => {
  const isLastPage =
    pageControl.value?.currentPage === pageControl.value?.totalPages;
  if (isLastPage) {
    return;
  }
  pageControl.value?.goToPage(pageControl.value?.currentPage + 1);
};
    
const handleKeyPress = (event: KeyboardEvent) => {
  if (event.key === "Enter") {
    handlePageInput(event);
  }
};

const isNextPageButtonDisable = computed(
  () => pageControl.value?.currentPage === pageControl.value?.totalPages,
);
    
const isPreviousPageButtonDisable = computed(
  () => pageControl.value?.currentPage === 1,
);
    
const printControl = computed(() => vpvRef.value?.printControl);

const handlePrintTool = () => {
  const printCtrl = unref(printControl);
  if (!printCtrl) return;
  printCtrl.print({ visibleDefaultProgress: true });
};

const cancelPrint = () => {
  const printCtrl = unref(printControl);
  if (!printCtrl) return;
  printCtrl.cancel();
};

watch(printControl, (printCtrl) => {
  if (!printCtrl) return;

  printCtrl.onProgress = (progress: any) => {
    console.log("Print progress", progress.percentage, progress.loadedPages);
  };

  printCtrl.onError = (error: any) => {
    console.log("Print error", error);
    cancelPrint();
  };

  printCtrl.onComplete = () => {
    console.log("Print completed");
  };
});
    
const downloadControl = computed(() => vpvRef.value?.downloadControl);

const handleDownloadFile = () => {
  const downloadCtrl = unref(downloadControl);
  if (!downloadCtrl) return;
  downloadCtrl.download();
};
    
watch(downloadControl, (downloadCtrl) => {
  if (!downloadCtrl) return;
  
  downloadCtrl.onError = (error: any) => {
    console.log("Download error", error);
  };
  
  downloadCtrl.onComplete = () => {
    console.log("Download completed");
  };
});
</script>
vue
<script setup>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, unref } from 'vue'

const vpvRef = ref(null)

const zoomControl = computed(() => vpvRef.value?.zoomControl)

const currentScale = computed(() => {
  return zoomControl.value?.scale
})
  
const handleZoomTool = (type) => {
  const zoomCtrl = unref(zoomControl)
  if (!zoomCtrl) return
  
  const scale = unref(currentScale)
  if (type === "in") {
    scale && zoomCtrl.zoom(scale + 0.25)
  } else if (type === "out") {
    scale && zoomCtrl.zoom(scale - 0.25)
  } else {
    zoomCtrl.zoom(type)
  }
}

const pageControl = computed(() => vpvRef.value?.pageControl)
const currentPageInput = computed(() => pageControl.value?.currentPage)
const searchControl = computed(() => vpvRef.value?.searchControl)
const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)

const isNextPageButtonDisable = computed(() => 
  pageControl.value?.currentPage === pageControl.value?.totalPages
)

const isPreviousPageButtonDisable = computed(() => 
  pageControl.value?.currentPage === 1
)

const prevPage = () => {
  const isFirstPage = pageControl.value?.currentPage === 1
  if (isFirstPage) return
  pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
}

const nextPage = () => {
  const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
  if (isLastPage) return
  pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
}

const handleKeyPress = (event) => {
  if (event.key === "Enter") {
    handlePageInput(event)
  }
}

const printControl = computed(() => vpvRef.value?.printControl)

const handlePrintTool = () => {
  const printCtrl = unref(printControl)
  if (!printCtrl) return
  printCtrl.print({ visibleDefaultProgress: true })
}

const cancelPrint = () => {
  const printCtrl = unref(printControl)
  if (!printCtrl) return
  printCtrl.cancel()
}

watch(printControl, (printCtrl) => {
  if (!printCtrl) return

  printCtrl.onProgress = (progress) => {
    console.log("Print progress", progress.percentage, progress.loadedPages)
  }

  printCtrl.onError = (error) => {
    console.log("Print error", error)
    cancelPrint()
  }

  printCtrl.onComplete = () => {
    console.log("Print completed")
  }
})

const downloadControl = computed(() => vpvRef.value?.downloadControl)

const handleDownloadFile = () => {
  const downloadCtrl = unref(downloadControl)
  if (!downloadCtrl) return
  downloadCtrl.download()
}

watch(downloadControl, (downloadCtrl) => {
  if (!downloadCtrl) return
  
  downloadCtrl.onError = (error) => {
    console.log("Download error", error)
  }
  
  downloadCtrl.onComplete = () => {
    console.log("Download completed")
  }
})
</script>
vue
<script lang="ts">
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref<InstanceType<typeof VPdfViewer>>()
    
    const zoomControl = computed(() => vpvRef.value?.zoomControl)
    const currentScale = computed(() => zoomControl.value?.scale)
    
    const handleZoomTool = (type: 'in' | 'out') => {
      const zoomCtrl = unref(zoomControl)
      if (!zoomCtrl) return
      
      const scale = unref(currentScale)
      if (type === 'in') {
        scale && zoomCtrl.zoom(scale + 0.25)
      } else if (type === 'out') {
        scale && zoomCtrl.zoom(scale - 0.25)
      } else {
        zoomCtrl.zoom(type)
      }
    }
    
    const pageControl = computed(() => vpvRef.value?.pageControl)
    const currentPageInput = computed(() => pageControl.value?.currentPage)
    const searchControl = computed(() => vpvRef.value?.searchControl)
    const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
    
    const isNextPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === pageControl.value?.totalPages
    )
    
    const isPreviousPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === 1
    )

    const prevPage = () => {
      const isFirstPage = pageControl.value?.currentPage === 1
      if (isFirstPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
    }

    const nextPage = () => {
      const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
      if (isLastPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
    }

    const handleKeyPress = (event: KeyboardEvent) => {
      if (event.key === "Enter") {
        handlePageInput(event)
      }
    }
    
    const printControl = computed(() => vpvRef.value?.printControl)

    const handlePrintTool = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.print({ visibleDefaultProgress: true })
    }

    const cancelPrint = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.cancel()
    }

    watch(printControl, (printCtrl) => {
      if (!printCtrl) return

      printCtrl.onProgress = (progress: any) => {
        console.log("Print progress", progress.percentage, progress.loadedPages)
      }

      printCtrl.onError = (error: any) => {
        console.log("Print error", error)
        cancelPrint()
      }

      printCtrl.onComplete = () => {
        console.log("Print completed")
      }
    })
      
     const downloadControl = computed(() => {
      return vpvRef.value?.downloadControl
    })
    
    const handleDownloadFile = () => {
      const downloadCtrl = downloadControl.value
      if (!downloadCtrl) return
      downloadCtrl.download()
    }
    
    watch(downloadControl, (downloadCtrl) => {
      if (!downloadCtrl) return
      
      downloadCtrl.onError = (error: any) => {
        console.log("Download error", error)
      }
      
      downloadCtrl.onComplete = () => {
        console.log("Download completed")
      }
    })

    return {
      vpvRef,
      handleZoomTool,
      zoomControl,
      currentScale,
      pageControl,
      currentPageInput,
      searchControl,
      totalMatches,
      prevPage,
      nextPage,
      handleKeyPress,
      isNextPageButtonDisable,
      isPreviousPageButtonDisable,
      printControl,
      handlePrintTool,
      cancelPrint,
      downloadControl,
      handleDownloadFile
    }
  }
})
</script>
vue
<script>
import { VPdfViewer } from '@vue-pdf-viewer/viewer'
import { ref, computed, defineComponent, unref } from 'vue'

export default defineComponent({
  components: { VPdfViewer },
  setup() {
    const vpvRef = ref(null)
    
    const zoomControl = computed(() => vpvRef.value?.zoomControl)
    const currentScale = computed(() => zoomControl.value?.scale)
    
    const handleZoomTool = (type) => {
      const zoomCtrl = unref(zoomControl)
      if (!zoomCtrl) return
      
      const scale = unref(currentScale)
      if (type === 'in') {
        scale && zoomCtrl.zoom(scale + 0.25)
      } else if (type === 'out') {
        scale && zoomCtrl.zoom(scale - 0.25)
      } else {
        zoomCtrl.zoom(type)
      }
    }
    
    const pageControl = computed(() => vpvRef.value?.pageControl)
    const currentPageInput = computed(() => pageControl.value?.currentPage)
    const searchControl = computed(() => vpvRef.value?.searchControl)
    const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
    
    const isNextPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === pageControl.value?.totalPages
    )
    
    const isPreviousPageButtonDisable = computed(() => 
      pageControl.value?.currentPage === 1
    )

    const prevPage = () => {
      const isFirstPage = pageControl.value?.currentPage === 1
      if (isFirstPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
    }

    const nextPage = () => {
      const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
      if (isLastPage) return
      pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
    }

    const handleKeyPress = (event) => {
      if (event.key === "Enter") {
        handlePageInput(event)
      }
    }
    
    const printControl = computed(() => vpvRef.value?.printControl)

    const handlePrintTool = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.print({ visibleDefaultProgress: true })
    }

    const cancelPrint = () => {
      const printCtrl = unref(printControl)
      if (!printCtrl) return
      printCtrl.cancel()
    }

    watch(printControl, (printCtrl) => {
      if (!printCtrl) return

      printCtrl.onProgress = (progress) => {
        console.log("Print progress", progress.percentage, progress.loadedPages)
      }

      printCtrl.onError = (error) => {
        console.log("Print error", error)
        cancelPrint()
      }

      printCtrl.onComplete = () => {
        console.log("Print completed")
      }
    })
      
    const downloadControl = computed(() => {
      return vpvRef.value?.downloadControl
    })
    
    const handleDownloadFile = () => {
      const downloadCtrl = downloadControl.value
      if (!downloadCtrl) return
      downloadCtrl.download()
    }
    
    watch(downloadControl, (downloadCtrl) => {
      if (!downloadCtrl) return
      
      downloadCtrl.onError = (error) => {
        console.log("Download error", error)
      }
      
      downloadCtrl.onComplete = () => {
        console.log("Download completed")
      }
    })

    return {
      vpvRef,
      handleZoomTool,
      zoomControl,
      currentScale,
      pageControl,
      currentPageInput,
      searchControl,
      totalMatches,
      prevPage,
      nextPage,
      handleKeyPress,
      isNextPageButtonDisable,
      isPreviousPageButtonDisable,
      printControl,
      handlePrintTool,
      cancelPrint,
      downloadControl,
      handleDownloadFile
    }
  }
})
</script>
vue
<template>
  <div class="py-2 px-2">
    <div class="flex flex-col gap-4 justify-self-center">
      <div class="flex items-center gap-4 text-[#7862FF] bg-pale-blue border-[#D7D1FB] rounded-lg p-2 justify-center">
                
        <button @click="() => handleZoomTool('out')">
          <i class="fa-solid fa-magnifying-glass-minus"></i>
        </button>
                
        <button @click="() => handleZoomTool('in')">
          <i class="fa-solid fa-magnifying-glass-plus"></i>
        </button>
                
        <button @click="prevPage" :disabled="isPreviousPageButtonDisable">
          <i class="fa-solid fa-chevron-up" />
        </button>

        <div class="flex items-center text-sm font-normal">
          <input 
            v-model="currentPageInput" 
            class="w-12 h-8 rounded-sm focus:outline-none pl-2" 
            @keypress="handleKeyPress"
          />
            <span class="pl-1">/{{ pageControl?.totalPages }}</span>
        </div>
        
        <button @click="nextPage" :disabled="isNextPageButtonDisable">
          <i class="fa-solid fa-chevron-down" />
        </button>
                
        <button @click="handlePrintTool">
          <i class="fa-solid fa-print" />
        </button>
                
        <button @click="handleDownloadFile">
          <i class="fa-solid fa-download" />
        </button>
      </div>
    </div>
  </div>
</template>