Skip to content

Conversation

@abidlabs
Copy link
Member

@abidlabs abidlabs commented Dec 9, 2025

We now support using regular old gr.Button instances as custom buttons inside components that have the buttons prop. These buttons appear in the top right toolbar for the component, next to standard icon buttons, and you can attach python or js events to these gr.Button's, which are triggered when the buttons in the toolbars are clicked. For example:

import random
import gradio as gr

with gr.Blocks() as demo:
    def refresh_data():
        return f"Refreshed content: {random.randint(1000, 9999)}"

    refresh_btn = gr.Button("Refresh")
    
    textbox = gr.Textbox(
        value="Sample text content that can be refreshed or copied.",
        buttons=["copy", refresh_btn],
        label="Sample Text",
        lines=5
    )        
    
    refresh_btn.click(refresh_data, outputs=textbox)
    
    demo.launch()

produces this:

Screen.Recording.2025-12-11.at.2.21.47.PM.mov

For a more complete example, see: demo/textbox_custom_buttons/run.py, which shows you can also attach js events like what you'd expect.

Screen.Recording.2025-12-11.at.2.23.32.PM.mov

This works for all components except for Dataframe and ImageEditor since those components need significant refactoring first I believe. Will handle them in a follow up PR.

Closes: #11878

AI disclosure: I implemented this mostly by hand for gr.Textbox and then used AI to extend to other components. I spot tested a variety of components to ensure behavior is as expected.

@gradio-pr-bot
Copy link
Collaborator

gradio-pr-bot commented Dec 9, 2025

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview
Website ready! Website preview
🦄 Changes detected! Details

Install Gradio from this PR

pip install https://gradio-pypi-previews.s3.amazonaws.com/b49195284ea2e3008b59c90c1c2579ca8b96a2a3/gradio-6.1.0-py3-none-any.whl

Install Gradio Python Client from this PR

pip install "gradio-client @ git+https://github.com/gradio-app/gradio@b49195284ea2e3008b59c90c1c2579ca8b96a2a3#subdirectory=client/python"

Install Gradio JS Client from this PR

npm install https://gradio-npm-previews.s3.amazonaws.com/b49195284ea2e3008b59c90c1c2579ca8b96a2a3/gradio-client-2.0.0.tgz

@gradio-pr-bot
Copy link
Collaborator

gradio-pr-bot commented Dec 9, 2025

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/annotatedimage minor
@gradio/atoms minor
@gradio/audio minor
@gradio/chatbot minor
@gradio/checkbox minor
@gradio/checkboxgroup minor
@gradio/code minor
@gradio/core minor
@gradio/datetime minor
@gradio/dialogue minor
@gradio/dropdown minor
@gradio/file minor
@gradio/fileexplorer minor
@gradio/gallery minor
@gradio/highlightedtext minor
@gradio/html minor
@gradio/image minor
@gradio/imageslider minor
@gradio/json minor
@gradio/label minor
@gradio/model3d minor
@gradio/number minor
@gradio/plot minor
@gradio/radio minor
@gradio/textbox minor
@gradio/utils minor
@gradio/video minor
gradio minor

  • Add ability to add custom buttons to components

✅ Changeset approved by @abidlabs

  • Maintainers can remove approval by unchecking this checkbox.

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

Copy link
Collaborator

@aliabid94 aliabid94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat API, worked well in my tests! Why is it not present in every component? Feel like it should be as ubiquitous as label=, not this subset of components.
Also think it could be wrapped up more neatly in the frontend in a reusable svelte component, seems like currently every frontend component is recreating this <IconButtonWrapper> with copy logic and looping through the other buttons.
Another nit, and may not be this PRs fault, but custom button borders look funny when in a form next to other components:
Screenshot 2025-12-12 at 1 04 28 PM

@abidlabs
Copy link
Member Author

Why is it not present in every component? Feel like it should be as ubiquitous as label=, not this subset of components.

Good point! Will implement this and fix the other issues on Monday

Copy link
Collaborator

@freddyaboulton freddyaboulton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great @abidlabs ! Agree with Ali's comments and left a few more.

register: app_tree.register_component.bind(app_tree),
dispatcher: gradio_event_dispatcher
dispatcher: gradio_event_dispatcher,
dispatch_to: dispatch_to_target
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a separate method for dispatching. Instead of doing dispatch_to inside each component, we can do gradio.dispatch("custom_button_click", {id: ..., ...}) and do the dispatch_to logic in the gradio_event_dispatcher function

let _props = $props();
const gradio = new Gradio<TextboxEvents, TextboxProps>(_props);
const gradio = new Gradio<
TextboxEvents | "custom_button_click",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type checking dispabled right now in the frontend but I think we should just add custom_button_click to each events class

@@ -0,0 +1,59 @@
<script lang="ts">
import { Image } from "@gradio/image/shared";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this introduces a circular dependency. @gradio/image depends on gradio/atoms and gradio/atoms depends on gradio/image. Not sure how the build is passing right now, but I think having this circular dependency will cause problems down the line eventually.

object-fit: contain;
}
.custom-button-label {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the font color is too light in light mode

image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would look better if it matched the label color

image

@abidlabs
Copy link
Member Author

abidlabs commented Dec 15, 2025

Thanks @freddyaboulton as well for the feedback! I'll add some docs for this as well.

@abidlabs
Copy link
Member Author

Also think it could be wrapped up more neatly in the frontend in a reusable svelte component, seems like currently every frontend component is recreating this with copy logic and looping through the other buttons

Moved most of the duplicated code into the <IconButtonWrapper> component itself here: 6a60ecd#diff-237ea6a0a29488f62c36b6b57715b87b182ccb22077894238e7cd754c57be2f6. There's still some previously existing duplicated code e.g. related to the copy button but I think it's better to leave it in each component since the copy implementations are not exactly the same

Another nit, and may not be this PRs fault, but custom button borders look funny when in a form next to other components:

I'm not sure how to fix it actually because it should only be applied when the component is a form and its not at the the "top" of the form, otherwise you have a duplicated, thicker border. I'll open a separate issue (as this was preexisting)

I think the font color is too light in light mode

I don't think we need a separate method for dispatching. Instead of doing dispatch_to inside each component, we can do gradio.dispatch("custom_button_click", {id: ..., ...}) and do the dispatch_to logic in the gradio_event_dispatcher function

Thanks for the suggestion! Both of these fixed in: 8dd9e01

I think this introduces a circular dependency. @gradio/image depends on gradio/atoms and gradio/atoms depends on gradio/image. Not sure how the build is passing right now, but I think having this circular dependency will cause problems down the line eventually.

I just removed the icon rendering. Emojis work better anyways. Fixed in 8ba76cb

Adding it now as a parameter in all of the other component

@abidlabs
Copy link
Member Author

Added to all components, here's how it looks for the Textbox:

image

and a few other components:

image

@abidlabs
Copy link
Member Author

Should be good to merge once CI is green. Will merge later tonight if there are no objections.

@abidlabs abidlabs merged commit f1d83fa into main Dec 16, 2025
19 of 20 checks passed
@abidlabs abidlabs deleted the custom-buttons branch December 16, 2025 02:08
@abidlabs abidlabs mentioned this pull request Dec 17, 2025
2 tasks
@light-and-ray
Copy link

Amazing! I was using patches via js for this before

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let Developers add custom buttons to the component Toolbar

7 participants