> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/gradio-app/gradio/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom component examples

> Learn from real-world examples of custom Gradio components

The best way to learn how to build custom components is to see them in action. This guide walks through a complete example of building a PDF display component from scratch.

## Case study: A component to display PDFs

Let's work through an example of building a custom Gradio component for displaying PDF files. This component will come in handy for showcasing [document question answering](https://huggingface.co/models?pipeline_tag=document-question-answering\&sort=trending) models, which typically work on PDF input.

This is a sneak preview of what our finished component will look like:

![PDF Display Demo](https://gradio-builds.s3.amazonaws.com/assets/PDFDisplay.png)

## Prerequisites

Make sure you have:

* Gradio 5.0 or higher
* Node 20+
* Read the [quickstart guide](/custom-components/quickstart)
* Read the [key concepts](/custom-components/key-concepts) guide

## Step 1: Create the custom component

Navigate to a directory of your choosing and run:

```bash theme={null}
gradio cc create PDF
```

This will create a subdirectory called `pdf` with the following structure:

```
- backend/     ← Python code
- frontend/    ← JavaScript code  
- demo/        ← Sample app
```

## Step 2: Add JavaScript dependencies

We're going to use the [pdfjs](https://mozilla.github.io/pdf.js/) library to display PDFs in the frontend. From within the `frontend` directory, run:

```bash theme={null}
npm install @gradio/client @gradio/upload @gradio/icons @gradio/button
npm install --save-dev pdfjs-dist@3.11.174
npm uninstall @zerodevx/svelte-json-view
```

Your `package.json` should now include these dependencies:

```json theme={null}
{
  "dependencies": {
    "@gradio/atoms": "0.2.0",
    "@gradio/statustracker": "0.3.0",
    "@gradio/utils": "0.2.0",
    "@gradio/client": "0.7.1",
    "@gradio/upload": "0.3.2",
    "@gradio/icons": "0.2.0",
    "@gradio/button": "0.2.3"
  },
  "devDependencies": {
    "pdfjs-dist": "3.11.174"
  }
}
```

## Step 3: Launch the dev server

Run the dev command to launch the development server:

```bash theme={null}
gradio cc dev
```

You should see a link printed to your console:

```
Frontend Server (Go here): http://localhost:7861/
```

Click on that link to see your component in action. Changes to the frontend and backend will reflect instantaneously!

## Step 4: Build the frontend skeleton

In `Index.svelte`, add the following imports and props:

```typescript theme={null}
import { tick } from "svelte";
import type { Gradio } from "@gradio/utils";
import { Block, BlockLabel } from "@gradio/atoms";
import { File } from "@gradio/icons";
import { StatusTracker } from "@gradio/statustracker";
import type { LoadingStatus } from "@gradio/statustracker";
import type { FileData } from "@gradio/client";
import { Upload, ModifyUpload } from "@gradio/upload";

export let elem_id = "";
export let elem_classes: string[] = [];
export let visible = true;
export let value: FileData | null = null;
export let container = true;
export let scale: number | null = null;
export let root: string;
export let height: number | null = 500;
export let label: string;
export let min_width: number | undefined = undefined;
export let loading_status: LoadingStatus;
export let gradio: Gradio<{
    change: never;
    upload: never;
}>;

let _value = value;
let old_value = _value;
```

<Tip>
  The `gradio` object contains metadata about the application and utility methods. We define that our component will dispatch `change` and `upload` events.
</Tip>

### Add the upload UI

Replace the content below the `</script>` tag with:

```svelte theme={null}
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
    {#if loading_status}
        <StatusTracker
            autoscroll={gradio.autoscroll}
            i18n={gradio.i18n}
            {...loading_status}
        />
    {/if}
    <BlockLabel
        show_label={label !== null}
        Icon={File}
        float={value === null}
        label={label || "File"}
    />
    {#if _value}
        <ModifyUpload i18n={gradio.i18n} absolute />
    {:else}
        <Upload
            filetype={"application/pdf"}
            file_count="single"
            {root}
        >
            Upload your PDF
        </Upload>
    {/if}
</Block>
```

## Step 5: Add PDF rendering logic

Import pdfjs and set up the worker:

```typescript theme={null}
import pdfjsLib from "pdfjs-dist";
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdn.bootcss.com/pdf.js/3.11.174/pdf.worker.js";

let pdfDoc;
let numPages = 1;
let currentPage = 1;
let canvasRef;
```

Add the rendering functions:

```typescript theme={null}
async function get_doc(value: FileData) {
    const loadingTask = pdfjsLib.getDocument(value.url);
    pdfDoc = await loadingTask.promise;
    numPages = pdfDoc.numPages;
    render_page();
}

function render_page() {
    pdfDoc.getPage(currentPage).then(page => {
        const ctx = canvasRef.getContext('2d');
        ctx.clearRect(0, 0, canvasRef.width, canvasRef.height);
        let viewport = page.getViewport({ scale: 1 });
        let scale = height / viewport.height;
        viewport = page.getViewport({ scale: scale });

        const renderContext = {
            canvasContext: ctx,
            viewport,
        };
        canvasRef.width = viewport.width;
        canvasRef.height = viewport.height;
        page.render(renderContext);
    });
}

$: if(JSON.stringify(old_value) != JSON.stringify(_value)) {
    if (_value){
        get_doc(_value);
    }
    old_value = _value;
    gradio.dispatch("change");
}
```

<Tip>
  The `$:` syntax in Svelte is how you declare reactive statements. Whenever any of the inputs change, Svelte will automatically re-run that statement.
</Tip>

Add the canvas element:

```svelte theme={null}
<div class="pdf-canvas" style="height: {height}px">
    <canvas bind:this={canvasRef}></canvas>
</div>

<style>
    .pdf-canvas {
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>
```

## Step 6: Handle file upload and clear

Add event handlers:

```typescript theme={null}
async function handle_clear() {
    _value = null;
    await tick();
    gradio.dispatch("change");
}

async function handle_upload({detail}: CustomEvent<FileData>): Promise<void> {
    value = detail;
    await tick();
    gradio.dispatch("change");
    gradio.dispatch("upload");
}
```

Connect them to the Upload components:

```svelte theme={null}
<ModifyUpload i18n={gradio.i18n} on:clear={handle_clear} absolute />

<Upload
    on:load={handle_upload}
    filetype={"application/pdf"}
    file_count="single"
    {root}
>
    Upload your PDF
</Upload>
```

## Step 7: Add page navigation

Import the button component and add navigation functions:

```typescript theme={null}
import { BaseButton } from "@gradio/button";

function next_page() {
    if (currentPage >= numPages) {
        return;
    }
    currentPage++;
    render_page();
}

function prev_page() {
    if (currentPage == 1) {
        return;
    }
    currentPage--;
    render_page();
}
```

Add the button UI:

```svelte theme={null}
<div class="button-row">
    <BaseButton on:click={prev_page}>
        ⬅️
    </BaseButton>
    <span class="page-count"> {currentPage} / {numPages} </span>
    <BaseButton on:click={next_page}>
        ➡️
    </BaseButton>
</div>

<style>
    .button-row {
        display: flex;
        flex-direction: row;
        width: 100%;
        justify-content: center;
        align-items: center;
    }

    .page-count {
        margin: 0 10px;
        font-family: var(--font-mono);
    }
</style>
```

## Step 8: Implement the backend

In your component's Python file, update the code to:

```python theme={null}
from __future__ import annotations
from typing import Any, Callable

from gradio.components.base import Component
from gradio.data_classes import FileData

class PDF(Component):

    EVENTS = ["change", "upload"]
    data_model = FileData

    def __init__(self, value: Any = None, *,
                 height: int | None = None,
                 label: str | None = None,
                 show_label: bool | None = None,
                 container: bool = True,
                 scale: int | None = None,
                 min_width: int | None = None,
                 interactive: bool | None = None,
                 visible: bool = True,
                 elem_id: str | None = None,
                 elem_classes: list[str] | str | None = None,
                 render: bool = True):
        super().__init__(value, label=label,
                         show_label=show_label, container=container,
                         scale=scale, min_width=min_width,
                         interactive=interactive, visible=visible,
                         elem_id=elem_id, elem_classes=elem_classes,
                         render=render)
        self.height = height

    def preprocess(self, payload: FileData) -> str:
        return payload.path

    def postprocess(self, value: str | None) -> FileData:
        if not value:
            return None
        return FileData(path=value)

    def example_payload(self):
        return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf"

    def example_value(self):
        return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf"
```

## Step 9: Build and publish

Build your component:

```bash theme={null}
gradio cc build
```

This creates a `.whl` file in the `dist/` directory that anyone can install with `pip install <path-to-whl>`.

Publish to PyPI and HuggingFace Spaces:

```bash theme={null}
gradio cc publish
```

## Conclusion

You've built a complete custom component! You can now use it in any Gradio 4.0+ app:

```python theme={null}
import gradio as gr
from gradio_pdf import PDF

with gr.Blocks() as demo:
    pdf = PDF(label="Upload a PDF", interactive=True)
    name = gr.Textbox()
    pdf.upload(lambda f: f, pdf, name)

demo.launch()
```

## More examples

Explore this [collection](https://huggingface.co/collections/gradio/custom-components-65497a761c5192d981710b12) of custom components on the HuggingFace Hub to learn from other developers' code.

Need help? Join the Gradio community on the [HuggingFace Discord](https://discord.gg/hugging-face-879548962464493619).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.