> ## 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 HTML components

> Learn how to create custom components using gr.HTML with templates, styling, JavaScript, and event handling.

The `gr.HTML` component lets you create custom UI elements with HTML, CSS, and JavaScript. You can build everything from simple styled displays to interactive custom components.

## Basic HTML

At its simplest, you can display custom HTML:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    gr.HTML(value="<h1>Hello World!</h1>")

demo.launch()
```

## HTML templates

For dynamic content, use the `html_template` parameter. Templates support two syntaxes:

* **`${}`**: Custom JavaScript expressions
* **`{{}}`**: Handlebars templating for loops and conditionals

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    gr.HTML(
        value="John",
        html_template="<h1>Hello, {{value}}!</h1><p>${value.length} letters</p>"
    )

demo.launch()
```

This renders as:

```html theme={null}
<h1>Hello, John!</h1><p>4 letters</p>
```

### Templating with lists

Use Handlebars loops to iterate over arrays:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    gr.HTML(
        value=["apple", "banana", "cherry"],
        html_template="""
            <h1>${value.length} fruits:</h1>
            <ul>
              {{#each value}}
                <li>{{this}}</li>
              {{/each}}
            </ul>
        """
    )

demo.launch()
```

## Adding CSS styles

Use the `css_template` parameter to style your HTML:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    gr.HTML(
        value=3,
        html_template="""
            <h2>Star Rating:</h2>
            <div class='stars'>
                {{#each (range 1 6)}}
                    <img class='{{#if (lte this ../value)}}filled{{else}}empty{{/if}}'
                         src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>
                {{/each}}
            </div>
        """,
        css_template="""
            img { height: 50px; display: inline-block; }
            .empty { filter: grayscale(100%); opacity: 0.3; }
        """
    )

demo.launch()
```

<Note>
  By default, `gr.HTML` applies some CSS to match the Gradio theme. Disable this with `apply_default_css=False` if you want full control over styling.
</Note>

## Dynamic updates with templates

Templates automatically update when you change the value:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    rating = gr.HTML(
        value=3,
        html_template="""
            <h2>Star Rating:</h2>
            <div class='stars'>
                {{#each (range 1 6)}}
                    <img class='{{#if (lte this ../value)}}filled{{else}}empty{{/if}}'
                         src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>
                {{/each}}
            </div>
        """,
        css_template="""
            img { height: 50px; display: inline-block; }
            .empty { filter: grayscale(100%); opacity: 0.3; }
        """
    )
    
    slider = gr.Slider(1, 5, value=3, step=1, label="Select Rating")
    slider.change(lambda x: x, slider, rating)

demo.launch()
```

## Additional props

You can pass extra properties beyond `value` to your templates:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    rating = gr.HTML(
        value=3,
        max_stars=7,
        size="60px",
        html_template="""
            <h2>Star Rating: {{value}}/{{max_stars}}</h2>
            <div class='stars'>
                {{#each (range 1 (add max_stars 1))}}
                    <img class='{{#if (lte this ../value)}}filled{{else}}empty{{/if}}'
                         src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>
                {{/each}}
            </div>
        """,
        css_template="""
            img { height: {{size}}; display: inline-block; }
            .empty { filter: grayscale(100%); opacity: 0.3; }
        """
    )
    
    gr.Slider(1, 7, value=3, step=1, label="Rating").change(
        lambda x: x, inputs=rating, outputs=rating
    )

demo.launch()
```

All props are available in both `html_template` and `css_template`, and any prop can be updated via event listeners.

## Triggering events and custom inputs

To create interactive custom components, use the `js_on_load` parameter. This JavaScript code runs when the component loads and has access to:

* **`props`**: All component properties including `value`
* **`trigger(event_name, data)`**: Function to trigger events that Gradio can listen to

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    star_rating = gr.HTML(
        value=0,
        html_template="""
            <div class='star-container'>
                <h2>Rate this: <span id='rating-value'>{{value}}</span>/5</h2>
                <div class='stars'>
                    {{#each (range 1 6)}}
                        <img class='star {{#if (lte this ../value)}}filled{{else}}empty{{/if}}'
                             data-rating='{{this}}'
                             src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>
                    {{/each}}
                </div>
                <button id='submit-rating'>Submit Rating</button>
            </div>
        """,
        css_template="""
            .star { height: 50px; cursor: pointer; display: inline-block; }
            .empty { filter: grayscale(100%); opacity: 0.3; }
            .star:hover { transform: scale(1.2); }
            #submit-rating { margin-top: 10px; padding: 8px 16px; }
        """,
        js_on_load="""
            // Access the container element
            const container = element.querySelector('.star-container');
            
            // Add click listeners to stars
            container.querySelectorAll('.star').forEach(star => {
                star.addEventListener('click', () => {
                    const rating = parseInt(star.dataset.rating);
                    props.value = rating;  // Update the value
                });
            });
            
            // Add submit button listener
            container.querySelector('#submit-rating').addEventListener('click', () => {
                trigger('submit');  // Trigger a 'submit' event
            });
        """
    )
    
    output = gr.Textbox(label="Your Rating")
    
    # Listen to the custom 'submit' event
    star_rating.submit(lambda x: f"You rated: {x}/5", star_rating, output)

demo.launch()
```

<Note>
  **Important**: Event listeners attached in `js_on_load` are only attached once when the component first renders. If you create new elements dynamically that need listeners, use event delegation:

  ```javascript theme={null}
  element.addEventListener('click', (e) => {
      if (e.target && e.target.matches('.child-element')) {
          props.value = e.target.dataset.value;
      }
  });
  ```
</Note>

## Uploading files

The `js_on_load` scope includes an `upload()` function that uploads JavaScript `File` objects to the Gradio server:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    file_uploader = gr.HTML(
        value=None,
        html_template="""
            <div>
                <h3>Custom File Uploader</h3>
                <input type='file' id='file-input' />
                <button id='upload-btn'>Upload</button>
                <div id='status'></div>
            </div>
        """,
        js_on_load="""
            const fileInput = element.querySelector('#file-input');
            const uploadBtn = element.querySelector('#upload-btn');
            const status = element.querySelector('#status');
            
            uploadBtn.addEventListener('click', async () => {
                const file = fileInput.files[0];
                if (!file) {
                    status.textContent = 'Please select a file';
                    return;
                }
                
                status.textContent = 'Uploading...';
                
                // Upload the file
                const { path, url } = await upload(file);
                
                props.value = { path, url, name: file.name };
                status.textContent = 'Upload complete!';
                trigger('upload');
            });
        """
    )
    
    output = gr.JSON(label="Uploaded File Info")
    file_uploader.upload(lambda x: x, file_uploader, output)

demo.launch()
```

The `upload()` function returns a dictionary with:

* **`path`**: Server-side file path
* **`url`**: Public URL to access the file

## Server functions

You can call Python functions directly from your `js_on_load` code using `server_functions`:

```python theme={null}
import gradio as gr
import random

def get_random_quote():
    quotes = [
        "The only way to do great work is to love what you do.",
        "Innovation distinguishes between a leader and a follower.",
        "Stay hungry, stay foolish."
    ]
    return random.choice(quotes)

def save_quote(quote):
    # Save to database or file
    return f"Saved: {quote}"

with gr.Blocks() as demo:
    quote_component = gr.HTML(
        value="",
        html_template="""
            <div>
                <blockquote id='quote'>{{value}}</blockquote>
                <button id='new-quote'>New Quote</button>
                <button id='save-quote'>Save Quote</button>
                <div id='status'></div>
            </div>
        """,
        server_functions=[get_random_quote, save_quote],
        js_on_load="""
            const status = element.querySelector('#status');
            
            element.querySelector('#new-quote').addEventListener('click', async () => {
                // Call Python function from JavaScript
                const quote = await server.get_random_quote();
                props.value = quote;
            });
            
            element.querySelector('#save-quote').addEventListener('click', async () => {
                const result = await server.save_quote(props.value);
                status.textContent = result;
            });
            
            // Load initial quote
            server.get_random_quote().then(quote => props.value = quote);
        """
    )

demo.launch()
```

<Note>
  Server functions are available as async methods on the `server` object inside `js_on_load`. They can accept any JSON-serializable arguments and return JSON-serializable values.
</Note>

## Creating reusable component classes

For components you'll use multiple times, create a class by subclassing `gr.HTML`:

```python theme={null}
import gradio as gr
from gradio.data_classes import GradioModel

class StarRating(gr.HTML):
    def __init__(self, value=0, max_stars=5, **kwargs):
        super().__init__(
            value=value,
            max_stars=max_stars,
            html_template="""
                <div class='star-rating'>
                    {{#each (range 1 (add max_stars 1))}}
                        <img class='star {{#if (lte this ../value)}}filled{{else}}empty{{/if}}'
                             data-rating='{{this}}'
                             src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>
                    {{/each}}
                </div>
            """,
            css_template="""
                .star { height: 40px; cursor: pointer; display: inline-block; }
                .empty { filter: grayscale(100%); opacity: 0.3; }
                .star:hover { transform: scale(1.2); }
            """,
            js_on_load="""
                element.querySelectorAll('.star').forEach(star => {
                    star.addEventListener('click', () => {
                        props.value = parseInt(star.dataset.rating);
                        trigger('change');
                    });
                });
            """,
            **kwargs  # Important: pass through kwargs to parent
        )

# Now you can reuse it easily
with gr.Blocks() as demo:
    rating1 = StarRating(value=3, max_stars=5)
    rating2 = StarRating(value=4, max_stars=10)
    output = gr.Textbox()
    
    rating1.change(lambda x: f"Rating 1: {x}", rating1, output)

demo.launch()
```

<Warning>
  **Important**: Gradio requires all components to accept certain arguments (like `render`, `key`, `elem_id`, etc.). Always add `**kwargs` to your `__init__` method and pass it to `super().__init__()` to ensure your component works correctly.
</Warning>

## Embedding components in HTML

You can embed other Gradio components inside your HTML using the `@children` placeholder:

```python theme={null}
import gradio as gr

with gr.Blocks() as demo:
    with gr.HTML(
        html_template="""
            <div class='fancy-container'>
                <h2>User Information</h2>
                <div class='form-fields'>@children</div>
                <p class='help-text'>Please fill out all fields</p>
            </div>
        """,
        css_template="""
            .fancy-container {
                border: 2px solid #4CAF50;
                border-radius: 10px;
                padding: 20px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            }
            .form-fields { margin: 20px 0; }
            .help-text { color: #ccc; font-style: italic; }
        """
    ):
        name = gr.Textbox(label="Name")
        email = gr.Textbox(label="Email")

demo.launch()
```

<Note>
  The `@children` placeholder must be at the top level of the `html_template`. Target the parent element directly with CSS or JavaScript to style or interact with the container.
</Note>

## API and MCP support

To make your custom HTML component work with Gradio's API and MCP (Model Context Protocol), define how its data should be serialized.

### Option 1: Define api\_info method

```python theme={null}
class StarRating(gr.HTML):
    def api_info(self):
        return {
            "type": "integer",
            "minimum": 0,
            "maximum": self.max_stars,
            "description": "Star rating value"
        }
```

### Option 2: Define a Pydantic data model

For complex data structures, use a Pydantic model:

```python theme={null}
from gradio.data_classes import GradioModel, GradioRootModel
from typing import List

class RatingData(GradioModel):
    value: int
    max_stars: int
    comments: List[str]

class DetailedStarRating(gr.HTML):
    data_model = RatingData
```

Use `GradioModel` for dictionary-like data with named fields, or `GradioRootModel` for simple types (strings, lists, etc.) that don't need wrapping.

## Security considerations

<Warning>
  **Important security notes**:

  1. **XSS vulnerabilities**: Using `gr.HTML` injects raw HTML and JavaScript into your app. Never use untrusted user input directly in `html_template` or `js_on_load`.

  2. **Input validation**: Python event listeners that accept your `gr.HTML` component as input can receive arbitrary values, not just the values your frontend sets. Always sanitize and validate input in production apps.

  3. **Trust your code**: Only use `gr.HTML` with code you trust and control.
</Warning>

## Next steps

Explore example custom components:

* [Custom HTML component examples on GitHub](https://github.com/gradio-app/gradio/tree/main/gradio/components/custom_html_components)
* Learn about [custom CSS and JavaScript](/guides/custom-css-js) for app-wide styling
* Build [dynamic apps](/guides/dynamic-apps) that create HTML components on the fly


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