Skip to main content
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:
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
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:
<h1>Hello, John!</h1><p>4 letters</p>

Templating with lists

Use Handlebars loops to iterate over arrays:
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:
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()
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.

Dynamic updates with templates

Templates automatically update when you change the value:
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:
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
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()
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:
element.addEventListener('click', (e) => {
    if (e.target && e.target.matches('.child-element')) {
        props.value = e.target.dataset.value;
    }
});

Uploading files

The js_on_load scope includes an upload() function that uploads JavaScript File objects to the Gradio server:
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:
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()
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.

Creating reusable component classes

For components you’ll use multiple times, create a class by subclassing gr.HTML:
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()
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.

Embedding components in HTML

You can embed other Gradio components inside your HTML using the @children placeholder:
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()
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.

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

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:
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

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.

Next steps

Explore example custom components: