> ## 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.

# Building dynamic apps with the render decorator

> Learn how to create dynamic Gradio applications where components and event listeners can change based on user input.

By default, the components and event listeners you define in a Blocks app are fixed once the app launches. The `@gr.render` decorator changes this, allowing you to dynamically create, modify, or remove components based on user interactions.

## Dynamic number of components

Let's start with a simple example that creates a variable number of textboxes:

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

with gr.Blocks() as demo:
    input_text = gr.Textbox(label="input")
    
    @gr.render(inputs=input_text)
    def show_split(text):
        if len(text) == 0:
            gr.Markdown("## No Input Provided")
        else:
            for letter in text:
                gr.Textbox(letter)

demo.launch()
```

As you type in the input textbox, a new textbox is created for each letter. The `@gr.render` decorator enables this with three simple steps:

<Steps>
  <Step title="Create a function and add the decorator">
    Wrap your function with `@gr.render` to make it re-render dynamically.
  </Step>

  <Step title="Specify inputs">
    Add input components to the `inputs=` parameter. The function will automatically re-run when any input changes.
  </Step>

  <Step title="Create components inside the function">
    Any components created inside the function will be rendered based on the inputs.
  </Step>
</Steps>

Whenever the inputs change, the function re-runs and replaces the previous components with the new ones.

### Customizing triggers

By default, `@gr.render` re-runs on:

* The `.load` event of the app
* The `.change` event of any input component

You can override this with custom triggers:

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

with gr.Blocks() as demo:
    input_text = gr.Textbox(label="input")
    
    @gr.render(inputs=input_text, triggers=[input_text.submit])
    def show_split(text):
        if len(text) == 0:
            gr.Markdown("## No Input Provided")
        else:
            for letter in text:
                gr.Textbox(letter)

demo.launch()
```

Now the function only re-runs when you press Enter in the textbox.

<Note>
  If you set custom triggers and want an automatic render at app start, add `demo.load` to your list of triggers.
</Note>

## Dynamic event listeners

When you create components inside a render function, you can also attach event listeners to them:

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

with gr.Blocks() as demo:
    text_count = gr.State(1)
    add_btn = gr.Button("Add Textbox")
    merge_btn = gr.Button("Merge")
    output = gr.Textbox(label="Merged Output")
    
    add_btn.click(lambda x: x + 1, text_count, text_count)
    
    @gr.render(inputs=text_count)
    def render_textboxes(count):
        textboxes = []
        for i in range(count):
            textbox = gr.Textbox(label=f"Textbox {i+1}", key=f"textbox_{i}")
            textboxes.append(textbox)
        
        # Event listener must be inside the render function
        merge_btn.click(
            lambda *texts: " ".join(texts),
            inputs=textboxes,
            outputs=output
        )

demo.launch()
```

<Warning>
  **Critical rule**: All event listeners that use components created inside a render function must also be defined inside that render function.

  However, these event listeners can still reference components defined outside the render function (like `merge_btn` and `output` in the example above).
</Warning>

When the function re-renders:

1. Event listeners from the previous render are cleared
2. New event listeners from the latest run are attached
3. The number of textboxes matches the current `text_count` value

## Understanding the key parameter

The `key=` parameter tells Gradio that the same component is being recreated across re-renders. This provides two benefits:

<Steps>
  <Step title="Browser performance">
    The same DOM element is reused instead of being destroyed and rebuilt, which is faster and preserves browser attributes.
  </Step>

  <Step title="Property preservation">
    Properties that may change (like `value`) are preserved across re-renders instead of being reset.
  </Step>
</Steps>

By default, only the `value` property is preserved. You can specify additional properties with `preserved_by_key=`:

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

with gr.Blocks() as demo:
    number_of_boxes = gr.Slider(
        1, 5, value=2, step=1, label="Number of Textboxes"
    )
    
    @gr.render(inputs=number_of_boxes)
    def render_boxes(count):
        for i in range(count):
            textbox = gr.Textbox(
                label=f"Box {i+1}",
                info=f"This is textbox number {i+1}",
                key=f"box_{i}",
                preserved_by_key=["value", "label"]
            )
            
            gr.Button("Change Label").click(
                lambda: gr.Textbox(label="Updated!", info="Info resets"),
                outputs=textbox
            )

demo.launch()
```

In this example:

* When you change the slider, textboxes re-render
* Any entered `value` is preserved (because it's preserved by default)
* Any changed `label` is preserved (because we added it to `preserved_by_key`)
* The `info` property resets (because it's not in `preserved_by_key`)

<Note>
  **Parent layouts must also be keyed**: If your component is nested within layout items like `gr.Row` or `gr.Column`, make sure to key those parent elements as well. The keys of all parent elements must match for the component to be properly preserved.
</Note>

### Keying event listeners

You can also key event listeners to improve performance and prevent errors:

```python theme={null}
button.click(
    some_function,
    inputs=input_box,
    outputs=output_box,
    key="my_event"
)
```

This is useful when:

* The same listener is recreated with the same inputs and outputs across renders
* An event from a previous render might finish after a re-render occurs

Keying ensures Gradio knows where to send the data properly.

## Building a todo list app

Let's put everything together with a complete example:

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

with gr.Blocks() as demo:
    tasks = gr.State([])  # Store tasks as ["task text", completed (bool)]
    new_task = gr.Textbox(label="New Task", placeholder="Enter a task...")
    add_btn = gr.Button("Add Task")
    
    def add_task(task_text, current_tasks):
        if task_text.strip():
            return current_tasks + [[task_text, False]]
        return current_tasks
    
    add_btn.click(add_task, [new_task, tasks], [tasks])
    new_task.submit(add_task, [new_task, tasks], [tasks])
    
    @gr.render(inputs=tasks)
    def render_todos(task_list):
        if not task_list:
            gr.Markdown("*No tasks yet. Add one above!*")
            return
        
        for i, (task_text, completed) in enumerate(task_list):
            with gr.Row(key=f"task_{i}"):
                checkbox = gr.Checkbox(
                    value=completed,
                    label=task_text,
                    container=False,
                    key=f"checkbox_{i}"
                )
                delete_btn = gr.Button(
                    "🗑️",
                    scale=0,
                    min_width=50,
                    key=f"delete_{i}"
                )
                
                def mark_done(checked, task=task_list[i]):
                    task[1] = checked
                    return task_list
                
                def delete(task=task_list[i]):
                    task_list.remove(task)
                    return task_list
                
                checkbox.change(
                    mark_done,
                    inputs=checkbox,
                    outputs=tasks,
                    key=f"mark_{i}"
                )
                delete_btn.click(
                    delete,
                    outputs=tasks,
                    key=f"delete_click_{i}"
                )

demo.launch()
```

<Note>
  **Important pattern**: When a variable from a loop is used inside an event listener function, "freeze" it by setting it as a default argument:

  ```python theme={null}
  def mark_done(checked, task=task_list[i]):  # task=task_list[i] freezes the value
      ...
  ```

  This ensures each listener uses the correct loop-time value, not the final value after the loop completes.
</Note>

### Working with nested state

When your render function reacts to a list or dict in `gr.State`:

<Steps>
  <Step title="Set state as output">
    Any event listener that modifies the state must include the state variable as an output, even if the function modifies it in-place. This tells Gradio to check if the state has changed.
  </Step>

  <Step title="Freeze loop variables">
    Use default arguments to freeze variables that are used inside event listeners within loops.
  </Step>
</Steps>

## Building an audio mixer

Here's a more advanced example that demonstrates all the concepts:

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

def mix_audio(audio_dict):
    """Mix multiple audio tracks with volume controls."""
    if not audio_dict:
        return None
    
    # Find the longest audio track
    max_length = max(a[0].shape[0] if a else 0 
                     for a in audio_dict.values() if isinstance(a, tuple))
    
    if max_length == 0:
        return None
    
    # Get sample rate from first audio
    sample_rate = next(
        a[1] for a in audio_dict.values() 
        if isinstance(a, tuple) and a is not None
    )
    
    # Initialize mixed audio array
    mixed = np.zeros(max_length)
    
    for component, value in audio_dict.items():
        if isinstance(value, tuple) and value is not None:
            audio_data, sr = value
            volume = audio_dict.get(f"{component}_volume", 1.0)
            
            # Pad or trim to match max_length
            if audio_data.shape[0] < max_length:
                audio_data = np.pad(
                    audio_data, 
                    (0, max_length - audio_data.shape[0])
                )
            else:
                audio_data = audio_data[:max_length]
            
            # Apply volume and add to mix
            mixed += audio_data * volume
    
    # Normalize to prevent clipping
    if np.max(np.abs(mixed)) > 0:
        mixed = mixed / np.max(np.abs(mixed))
    
    return (sample_rate, mixed)

with gr.Blocks() as demo:
    track_count = gr.State(1)
    output_audio = gr.Audio(label="Mixed Output")
    
    with gr.Row():
        add_track_btn = gr.Button("Add Track")
        mix_btn = gr.Button("Mix Tracks", variant="primary")
    
    add_track_btn.click(
        lambda x: x + 1,
        inputs=track_count,
        outputs=track_count
    )
    
    @gr.render(inputs=track_count)
    def render_tracks(count):
        audio_inputs = set()
        
        for i in range(count):
            with gr.Row(key=f"track_row_{i}"):
                audio = gr.Audio(
                    label=f"Track {i+1}",
                    key=f"audio_{i}"
                )
                volume = gr.Slider(
                    0, 2, value=1,
                    label="Volume",
                    key=f"volume_{i}"
                )
                audio_inputs.add(audio)
                audio_inputs.add(volume)
        
        # Event listener uses all dynamically created components
        mix_btn.click(
            mix_audio,
            inputs=audio_inputs,
            outputs=output_audio,
            key="mix_event"
        )

demo.launch()
```

<Note>
  **Using sets for many inputs**: When you have many components of different types as inputs, use set notation (`inputs={comp1, comp2, ...}`) instead of list notation. In your function, you'll receive a dictionary where keys are the components and values are their current values.
</Note>

## Best practices

<Steps>
  <Step title="Always use keys">
    Key all components and layouts inside render functions to preserve state and improve performance.
  </Step>

  <Step title="Freeze loop variables">
    When using loop variables inside event listeners, freeze them with default arguments: `func(task=task)`.
  </Step>

  <Step title="Set state as output">
    When modifying state variables (especially nested structures), always include them in the event listener's outputs.
  </Step>

  <Step title="Define listeners inside render">
    Event listeners that use dynamically created components must be defined inside the render function.
  </Step>

  <Step title="Key event listeners">
    Key your event listeners when the same listener is recreated across renders to prevent errors and improve performance.
  </Step>
</Steps>

## Next steps

The `@gr.render` decorator dramatically expands what you can build with Gradio. Now you can:

* Explore [custom HTML components](/guides/custom-html) for even more flexibility
* Learn about [state management](/guides/state-in-blocks) for complex applications
* Build [multi-step workflows](/guides/controlling-layout) with dynamic layouts


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