Read More on Telerik Blogs
August 25, 2025 Blazor, Web
Get A Free Trial

In this article, you will learn how to display PDF documents on your websites created with Blazor, thanks to the Telerik PdfViewer control. Undoubtedly, this is a very useful feature when you want your users to easily navigate through PDF documents of interest, without the need to download them.

Let’s get started!

What Is the Blazor PDF Viewer Control?

The Blazor PDF Viewer control is a Progress Telerik UI for Blazor component that allows you to open PDF documents in users’ browsers from your own application. One great advantage of this component is that it offers features such as pagination, zooming, printing, text selection, searching, among others. Some practical cases where you could use this component include:

  • Viewing invoices
  • Reading documents behind a payment gateway
  • Previews of uploaded documents
  • User manuals
  • Reading documents that are part of a project
  • Among many others

Additionally, since it is possible to use the .NET MAUI Blazor Hybrid template to reuse Blazor components in .NET MAUI, you could create cross-platform solutions without the headaches that often come with displaying PDF documents in mobile applications.

Working with the Telerik PdfViewer Component

Let’s see how to work with the PdfViewer component. For this, let’s assume we are updating a portal where we will allow users to generate purchase invoices.

To avoid issues when using the PdfViewer component, make sure to configure your project according to the Blazor components installation guide. Once you have done that, create a new component and replace its content with the following code:

@page "/invoice-generator"
@using System.Globalization
@inject HttpClient Http
@inject NavigationManager NavigationManager

<PageTitle>Invoice Generator</PageTitle>

<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <h1 class="mb-4 text-center">Invoice Generator</h1>
        </div>
    </div>
    @if (showPdfPlaceholder)
    {
        <div class="row">
            <div class="col-12">
                <div class="pdf-placeholder">
                    <div class="pdf-content">
                        <TelerikSvgIcon Icon="@SvgIcon.FilePdf" Size="@ThemeConstants.SvgIcon.Size.ExtraLarge" />
                        <h3 class="mt-3">PDF Invoice here</h3>
                        <p class="text-muted">Your generated invoice will appear here</p>
                    </div>
                </div>
            </div>
        </div>
    }
    else if (isGenerating)
    {
        <div class="row">
            <div class="col-12">
                <div class="loader-overlay">
                    <div class="loader-content">
                        <TelerikLoader Type="@LoaderType.Pulsing"
                                       Size="@ThemeConstants.Loader.Size.Large"
                                       ThemeColor="@ThemeConstants.Loader.ThemeColor.Primary" />
                        <h3 class="mt-4">Generating Invoice...</h3>
                        <p class="text-muted">Please wait while we create your PDF invoice</p>
                    </div>
                </div>
            </div>
        </div>
    }
    else
    {
        <div class="row justify-content-center">
            <div class="col-lg-8 col-xl-6">
                <div class="invoice-form-card">
                    <EditForm Model="@invoiceModel" OnValidSubmit="@OnValidSubmit" class="invoice-form">
                        <DataAnnotationsValidator />
                        <ValidationSummary />
                        
                        <div class="form-section">
                            <h4 class="section-title">Company Information</h4>
                            <div class="row g-3">
                                <div class="col-12">
                                    <label class="form-label">Company Name</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CompanyName"
                                                    ReadOnly="true"
                                                    Class="readonly-field" />
                                </div>

                                <div class="col-12">
                                    <label class="form-label">Company Address</label>
                                    <TelerikTextArea @bind-Value="@invoiceModel.CompanyAddress"
                                                     ReadOnly="true"
                                                     Rows="2"
                                                     Class="readonly-field" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Company Email</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CompanyEmail"
                                                    ReadOnly="true"
                                                    Class="readonly-field" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Company Phone</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CompanyPhone"
                                                    ReadOnly="true"
                                                    Class="readonly-field" />
                                </div>
                            </div>
                        </div>
                        
                        <div class="form-section">
                            <h4 class="section-title">Invoice Details</h4>
                            <div class="row g-3">
                                <div class="col-md-6">
                                    <label class="form-label">Invoice Number</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.InvoiceNumber"
                                                    ReadOnly="true"
                                                    Class="readonly-field" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Invoice Date</label>
                                    <TelerikDatePicker @bind-Value="@invoiceModel.InvoiceDate"
                                                       ReadOnly="true"
                                                       Class="readonly-field" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Due Date</label>
                                    <TelerikDatePicker @bind-Value="@invoiceModel.DueDate"
                                                       ReadOnly="true"
                                                       Class="readonly-field" />
                                </div>
                            </div>
                        </div>
                        
                        <div class="form-section">
                            <h4 class="section-title">Customer Information</h4>
                            <div class="row g-3">
                                <div class="col-12">
                                    <label class="form-label">Customer Name *</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CustomerName"
                                                    Placeholder="Enter customer name"
                                                    Class="editable-field" />
                                    <ValidationMessage For="@(() => invoiceModel.CustomerName)" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Customer Email *</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CustomerEmail"
                                                    Placeholder="customer@email.com"
                                                    Class="editable-field" />
                                    <ValidationMessage For="@(() => invoiceModel.CustomerEmail)" />
                                </div>

                                <div class="col-md-6">
                                    <label class="form-label">Customer Phone</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.CustomerPhone"
                                                    Placeholder="+1 (555) 123-4567"
                                                    Class="editable-field" />
                                </div>

                                <div class="col-12">
                                    <label class="form-label">Customer Address *</label>
                                    <TelerikTextArea @bind-Value="@invoiceModel.CustomerAddress"
                                                     Placeholder="Enter customer address"
                                                     Rows="2"
                                                     Class="editable-field" />
                                    <ValidationMessage For="@(() => invoiceModel.CustomerAddress)" />
                                </div>
                            </div>
                        </div>
                        
                        <div class="form-section">
                            <h4 class="section-title">Invoice Items</h4>
                            <div class="row g-3">
                                <div class="col-md-6">
                                    <label class="form-label">Item Description</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.ItemDescription"
                                                    ReadOnly="true"
                                                    Class="readonly-field" />
                                </div>

                                <div class="col-md-2">
                                    <label class="form-label">Quantity</label>
                                    <TelerikNumericTextBox @bind-Value="@invoiceModel.Quantity"
                                                           Min="1"
                                                           Max="999"
                                                           Class="editable-field" />
                                </div>                                <div class="col-md-2">
                                    <label class="form-label">Unit Price</label>
                                    <TelerikNumericTextBox @bind-Value="@invoiceModel.UnitPrice"
                                                           Min="0"
                                                           Format="$#,##0.00"
                                                           ReadOnly="true"
                                                           Class="readonly-field" />
                                </div><div class="col-md-2">
                                    <label class="form-label">Total Amount</label>
                                    <TelerikTextBox @bind-Value="@invoiceModel.TotalAmountDisplay"
                                                    ReadOnly="true"
                                                    Class="readonly-field total-field" />
                                </div>
                            </div>
                        </div>
                        
                        <div class="form-section">
                            <h4 class="section-title">Additional Information</h4>
                            <div class="row g-3">
                                <div class="col-12">
                                    <label class="form-label">Notes</label>
                                    <TelerikTextArea @bind-Value="@invoiceModel.Notes"
                                                     Placeholder="Add any additional notes or terms"
                                                     Rows="3"
                                                     Class="editable-field" />
                                </div>
                            </div>
                        </div>

                        <div class="text-center mt-4">
                            <TelerikButton ButtonType="@ButtonType.Submit"
                                           ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                                           Size="@ThemeConstants.Button.Size.Large"
                                           Class="generate-btn"
                                           Enabled="@(!isGenerating)">
                                @if (isGenerating)
                                {
                                    <TelerikLoader Type="@LoaderType.Pulsing" Size="@ThemeConstants.Loader.Size.Small" />
                                    <span class="ms-2">Generating Invoice...</span>
                                }
                                else
                                {
                                    <TelerikSvgIcon Icon="@SvgIcon.FilePdf" />
                                    <span class="ms-2">Generate Invoice</span>
                                }
                            </TelerikButton>
                        </div>
                    </EditForm>
                </div>
            </div>
        </div>
    }
</div>

@code {
    private InvoiceModel invoiceModel = new();
    private bool isGenerating = false;
    private bool showPdfPlaceholder = false;
    protected override void OnInitialized()
    {        
        invoiceModel.CompanyName = "Demo Solutions Inc.";
        invoiceModel.CompanyAddress = "123 Business Street\nSuite 456\nNew York, NY 10001";
        invoiceModel.CompanyEmail = "info@demosolutions.com";
        invoiceModel.CompanyPhone = "+1 (555) 123-4567";

        invoiceModel.InvoiceNumber = $"INV-{DateTime.Now:yyyyMMdd}-001";
        invoiceModel.InvoiceDate = DateTime.Now;
        invoiceModel.DueDate = DateTime.Now.AddDays(30);

        invoiceModel.ItemDescription = "Professional Consulting Services";
        
        invoiceModel.SetUnitPrice(150.00m);
        invoiceModel.SetQuantity(10);

        invoiceModel.Notes = "Payment due within 30 days. Thank you for your business!";
    }
    private async Task OnValidSubmit()
    {
        isGenerating = true;
        StateHasChanged();
        
        await Task.Delay(3000);

        isGenerating = false;
        showPdfPlaceholder = true;
        StateHasChanged();
    }

    public class InvoiceModel
    {        
        public string CompanyName { get; set; } = string.Empty;
        public string CompanyAddress { get; set; } = string.Empty;
        public string CompanyEmail { get; set; } = string.Empty;
        public string CompanyPhone { get; set; } = string.Empty;
        
        public string InvoiceNumber { get; set; } = string.Empty;
        public DateTime InvoiceDate { get; set; }
        public DateTime DueDate { get; set; }
        
        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Customer name is required")]
        [System.ComponentModel.DataAnnotations.StringLength(100, ErrorMessage = "Customer name cannot exceed 100 characters")]
        public string CustomerName { get; set; } = "Cool Solutions";

        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Customer email is required")]
        [System.ComponentModel.DataAnnotations.EmailAddress(ErrorMessage = "Please enter a valid email address")]
        public string CustomerEmail { get; set; } = "contact@coolsolutions.com";

        public string CustomerPhone { get; set; } = "+1 (555) 987-6543";

        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Customer address is required")]
        public string CustomerAddress { get; set; } = "456 Industrial Ave\nSuite 789\nLos Angeles, CA 90001";
        public string ItemDescription { get; set; } = string.Empty;

        private int _quantity = 1;
        public int Quantity
        {
            get => _quantity;
            set
            {
                _quantity = value;
                UpdateTotalAmount();
            }
        }

        private decimal _unitPrice = 0;
        public decimal UnitPrice
        {
            get => _unitPrice;
            set
            {
                _unitPrice = value;
                UpdateTotalAmount();
            }
        }

        public decimal TotalAmount => Quantity * UnitPrice;
        public string TotalAmountDisplay { get; set; } = "$0.00";

        public void SetUnitPrice(decimal price)
        {
            _unitPrice = price;
        }

        public void SetQuantity(int quantity)
        {
            _quantity = quantity;
            UpdateTotalAmount();
        }
        private void UpdateTotalAmount()
        {
            try
            {
                var total = TotalAmount;
                TotalAmountDisplay = $"${total:N2}";
            }
            catch
            {
                TotalAmountDisplay = "$0.00";
            }
        }
        
        public string Notes { get; set; } = string.Empty;
    }
}

<style>
    .invoice-form-card {
        background: white;
        border-radius: 12px;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
        padding: 2rem;
        margin-bottom: 2rem;
    }

    .invoice-form {
        max-width: 100%;
    }

    .form-section {
        margin-bottom: 2rem;
        padding: 1.5rem;
        border: 1px solid #e9ecef;
        border-radius: 8px;
        background-color: #fdfdfd;
    }

    .section-title {
        font-weight: 600;
        color: #495057;
        font-size: 1.1rem;
        margin-bottom: 1rem;
        border-bottom: 2px solid #007acc;
        padding-bottom: 0.5rem;
    }

    .form-label {
        font-weight: 500;
        color: #495057;
        margin-bottom: 0.5rem;
    }

    .readonly-field {
        background-color: #f8f9fa !important;
        border-color: #e9ecef !important;
        color: #6c757d;
    }

    .editable-field {
        border-color: #007acc;
    }

        .editable-field:focus {
            border-color: #0056b3;
            box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
        }

    .total-field {
        font-weight: bold;
        font-size: 1.1em;
        background-color: #e7f3ff !important;
    }

    .generate-btn {
        min-width: 200px;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
    }

    .pdf-placeholder {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 12px;
        min-height: 600px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
    }

    .pdf-content {
        text-align: center;
        padding: 2rem;
    }

        .pdf-content h3 {
            font-weight: 300;
            margin-bottom: 0.5rem;
        }

        .pdf-content p {
            opacity: 0.8;
            font-size: 1.1rem;
        }

    .loader-overlay {
        background: linear-gradient(135deg, #007acc 0%, #0056b3 100%);
        border-radius: 12px;
        min-height: 600px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
        animation: pulse-bg 2s ease-in-out infinite alternate;
    }

    .loader-content {
        text-align: center;
        padding: 2rem;
    }

        .loader-content h3 {
            font-weight: 400;
            margin-bottom: 0.5rem;
            font-size: 1.5rem;
        }

        .loader-content p {
            opacity: 0.9;
            font-size: 1.1rem;
            margin-bottom: 0;
        }

    @@keyframes pulse-bg {
        0% {
            background: linear-gradient(135deg, #007acc 0%, #0056b3 100%);
        }

        100% {
            background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
        }
    }
    
    @@media (max-width: 768px) {
        .invoice-form-card {
            padding: 1rem;
            margin: 0.5rem;
        }

        .pdf-placeholder,
        .loader-overlay {
            min-height: 400px;
            margin: 0.5rem;
        }

        .form-section {
            padding: 1rem;
        }

        .loader-content h3 {
            font-size: 1.3rem;
        }

        .loader-content p {
            font-size: 1rem;
        }
    }
</style>

In the code above, you can notice that I have used several Telerik components to create the customer form, such as TelerikSvgIcon, TelerikTextBox, TelerikTextArea, TelerikDatePicker, among others, as their flexibility allows for the rapid creation of graphical interfaces and even the addition of validations if needed.

The result is a beautiful graphical interface that prepares us for the implementation of the PdfViewer component:

Now, let’s see how to implement the PdfViewer control in our project.

Displaying a PDF with the PdfViewer Component

To add the PdfViewer component in a Blazor project, you need to add the TelerikPdfViewer tag as in the following example:

@if (showPdfPlaceholder)
{
    <div class="row">
        <div class="col-12">
            <div class="pdf-placeholder">
                <div class="pdf-content">

                    <TelerikPdfViewer />

                </div>
            </div>
        </div>
    </div>
}

This will display the component on the UI, allowing actions such as opening a PDF document:

In our case, remember that we want to load the simulated generated PDF document, so we will use the Data parameter, of type byte[], which can be used to load the content of the document we need. For our example, I have added a static PDF document in wwwroot, to load it in the component as follows:

...
    @if (showPdfPlaceholder)
    {
        <div class="row">
            <div class="col-12">
                <div class="pdf-placeholder">
                    <div class="pdf-content">

                        <TelerikPdfViewer Data="@PdfSource"/>

                    </div>
                </div>
            </div>
        </div>
    }
...

@code {   
    private byte[] PdfSource { get; set; }
    
    ...    
    
    private async Task OnValidSubmit()
    {
       ...

        
        var url = NavigationManager.ToAbsoluteUri("invoice.pdf");        
        var pdfBytes = await Http.GetByteArrayAsync(url);
        
        PdfSource = pdfBytes;

        showPdfPlaceholder = true;
        StateHasChanged();
    }

...

In the code above, we load the bytes of the document using the GetByteArrayAsync method, assigning its content to the previously defined PdfSource property. Since we have assigned this property to the Data parameter of the PdfViewer component, it is automatically opened:

Customizing the PdfViewer Toolbar

One thing you will likely want to do depending on the particular scenario in which you implement PdfViewer is to customize which tools you want your users to use. For example, if your application is to display invoices generated for your clients, there is no point in showing the tool to open other PDF documents.

You can see a list of all the tools supported by PdfViewer in the official documentation. For our practical example, we will only leave the Download and Print tools, which is very easy to do by setting the corresponding tags:

...
    <TelerikPdfViewer Data="@PdfSource">

        <PdfViewerToolBar>
            <PdfViewerToolBarDownloadTool />
            <PdfViewerToolBarPrintTool />
        </PdfViewerToolBar>

    </TelerikPdfViewer>
...

Similarly, if you cannot find the tool you need for your PDF, you can create it via the PdfViewerToolBarCustomTool tag, where you can add the code with which the user will interact. For example, let’s assume we want the user to be able to translate their invoices into Spanish simply by clicking a button:

...
    @if (showPdfPlaceholder)
    {
        <div class="row">
            <div class="col-12">
                <div class="pdf-placeholder">
                    <div class="pdf-content">                    
                        <TelerikPdfViewer Data="@PdfSource">
                            <PdfViewerToolBar>
                                <PdfViewerToolBarDownloadTool />
                                <PdfViewerToolBarPrintTool />

                                <PdfViewerToolBarCustomTool>
                                    <TelerikButton OnClick="@TranslateInvoice">Translate Invoice
                                    </TelerikButton>
                                </PdfViewerToolBarCustomTool>

                             </PdfViewerToolBar>
                        </TelerikPdfViewer>
                    </div>
                </div>
            </div>
        </div>
    }
...

@code {

...

    private async Task TranslateInvoice()
    {
        showPdfPlaceholder = false;
        isGenerating = true;
        StateHasChanged();
        await Task.Delay(3000);
        isGenerating = false;

        var translatedFile = NavigationManager.ToAbsoluteUri("Invoice_es.pdf");
        var bytes = await Http.GetByteArrayAsync(translatedFile);
        PdfSource = bytes;
        showPdfPlaceholder = true;
    }
...

This will allow us to add the functionality we need for our scenarios:

Available Events in the PdfViewer

The PdfViewer control has the following four events:

  • OnDownload: Fired when the user clicks the Download button.
  • OnError: Fired when an error occurs while trying to open a file, such as when attempting to load a corrupt file or incorrect format.
  • OnOpen: Fired when the user opens a file from the toolbar.
  • ZoomChanged: Fired when the user clicks the zoom in/out buttons.

Each of these events receives different types of arguments, which you can see below:

...
    @if (showPdfPlaceholder)
    {
        <div class="row">
            <div class="col-12">
                <div class="pdf-placeholder">
                    <div class="pdf-content">                    
                        <TelerikPdfViewer Data="@PdfSource"
                                OnDownload="OnDownload"
                                OnError="OnError"
                                OnOpen="OnOpen"
                                ZoomChanged="ZoomChanged">
                           ...
                        </TelerikPdfViewer>
                    </div>
                </div>
            </div>
        </div>
    }
...

@code {

...
    
    private void OnDownload(PdfViewerDownloadEventArgs args)
    {        
        args.FileName = $"Invoice_{DateTime.Now:yyyyMMdd_HHmmss}.pdf";
    }
    private void OnError(PdfViewerErrorEventArgs args)
    {
        Debug.WriteLine($"PDF Viewer Error: {args.Message}");
    }
    private void OnOpen(PdfViewerOpenEventArgs args)
    {
        Debug.WriteLine($"PDF Viewer Opened: {args.Files.FirstOrDefault()}");
       
    }
    private void ZoomChanged(decimal args)
    {
        Debug.WriteLine($"Zoom changed to: {args}");
    }

...

}

...

Without a doubt, there are scenarios where these events are extremely useful, such as in the previous code, where the downloaded file is renamed during the OnDownload event following a pattern set by us.

Other Useful Parameters of the PdfViewer Control

The PdfViewer control has other parameters that we can configure to further customize it. Some of these are:

Class: Allows customizing the component and overriding the theme
Height and Width: Allow setting the height and width of the control
MaxZoom / MinZoom: Allow assigning a maximum and minimum zoom level on the component
Zoom / ZoomRate: Allow obtaining and setting the current zoom level and the ZoomRate

Conclusion

Throughout this post, you have seen in a practical way how to implement the Telerik PdfViewer control in your Blazor-based projects. You have also learned how to customize the toolbar and load PDF documents. Furthermore, you have learned about the available events in the component and other useful parameters. It is time to replace those download links to your PDFs with a PdfViewer component and display the documents directly in your applications.

Don’t forget, Telerik UI for Blazor comes with a 30-day free trial:

Try Now


About the Author

Héctor Pérez

Héctor Pérez is a Microsoft MVP with more than 10 years of experience in software development. He is an independent consultant, working with business and government clients to achieve their goals. Additionally, he is an author of books and an instructor at El Camino Dev and Devs School.

 

Related Posts