Repository: gradio-app/gradio

Issue:
Allow currently selected `TabItem` to be set + updated
Currently there is no way to initialise or update the `Tabs` component to show a specific `TabItem` it always defaults to the first.

We need to implement the following:

- Add `selected` kwarg to `Tabs`
- Add `id` kwargs to `TabItem`
- Add an `update` method to `Tabs` that accepts a `selected` kwarg.
- Use the provided `TabItem` `id` as the actual id in the frontend
- Use the provided `selected` `id` passed to `Tabs` as the selected tab.


Discussion:

Resolving patch:
diff --git a/demo/blocks_plug/run.py b/demo/blocks_plug/run.py
--- a/demo/blocks_plug/run.py
+++ b/demo/blocks_plug/run.py
@@ -1,9 +1,14 @@
 import gradio as gr
 
+
+def change_tab():
+    return gr.Tabs.update(selected=2)
+
+
 identity_demo, input_demo, output_demo = gr.Blocks(), gr.Blocks(), gr.Blocks()
 
 with identity_demo:
-    gr.Interface(lambda x:x, "text", "text")
+    gr.Interface(lambda x: x, "text", "text")
 
 with input_demo:
     t = gr.Textbox(label="Enter your text here")
@@ -17,14 +22,15 @@
 
 with gr.Blocks() as demo:
     gr.Markdown("Three demos in one!")
-    with gr.Tabs():
-        with gr.TabItem("Text Identity"):
+    with gr.Tabs(selected=1) as tabs:
+        with gr.TabItem("Text Identity", id=0):
             identity_demo.render()
-        with gr.TabItem("Text Input"):
+        with gr.TabItem("Text Input", id=1):
             input_demo.render()
-        with gr.TabItem("Text Static"):
+        with gr.TabItem("Text Static", id=2):
             output_demo.render()
-    
+    btn = gr.Button("Change tab")
+    btn.click(inputs=None, outputs=tabs, fn=change_tab)
 
 if __name__ == "__main__":
     demo.launch()
diff --git a/gradio/layouts.py b/gradio/layouts.py
--- a/gradio/layouts.py
+++ b/gradio/layouts.py
@@ -96,6 +96,26 @@ class Tabs(BlockContext):
     Tabs context.
     """
 
+    def __init__(self, selected: Optional[int | str] = None, **kwargs):
+        """
+        Parameters:
+        label (str): The visual label for the tab
+        selected: (Optional[int | str]): The currently selected tab. Must correspdong to an id passed to the one of the child TabItems. Defaults to the first TabItem.
+        """
+        super().__init__(**kwargs)
+        self.selected = selected
+
+    def get_config(self):
+        return {"selected": self.selected, **super().get_config()}
+
+    def update(
+        selected: Optional[int | str] = None,
+    ):
+        return {
+            "selected": selected,
+            "__type__": "update",
+        }
+
     def change(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
         """
         Parameters:
@@ -113,12 +133,18 @@ class TabItem(BlockContext):
     components defined within the TabItem will be rendered within a tab.
     """
 
-    def __init__(self, label, **kwargs):
+    def __init__(self, label: str, id: Optional[int | str] = None, **kwargs):
+        """
+        Parameters:
+        label (str): The visual label for the tab
+        id: (Optional[int | str]): An optional identifier for the tab, required if you wish to control the selected tab from a predict function.
+        """
         super().__init__(**kwargs)
         self.label = label
+        self.id = id
 
     def get_config(self):
-        return {"label": self.label, **super().get_config()}
+        return {"label": self.label, "id": self.id, **super().get_config()}
 
     def select(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
         """


Test patch:
diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py
--- a/gradio/test_data/blocks_configs.py
+++ b/gradio/test_data/blocks_configs.py
@@ -27,11 +27,22 @@
                 "style": {},
             },
         },
-        {"id": 3, "type": "tabs", "props": {"visible": True, "style": {}}},
+        {
+            "id": 3,
+            "type": "tabs",
+            "props": {
+                "visible": True,
+                "style": {},
+            },
+        },
         {
             "id": 4,
             "type": "tabitem",
-            "props": {"label": "X-ray", "visible": True, "style": {}},
+            "props": {
+                "label": "X-ray",
+                "visible": True,
+                "style": {},
+            },
         },
         {
             "id": 5,
@@ -71,7 +82,11 @@
         {
             "id": 9,
             "type": "tabitem",
-            "props": {"label": "CT Scan", "visible": True, "style": {}},
+            "props": {
+                "label": "CT Scan",
+                "visible": True,
+                "style": {},
+            },
         },
         {
             "id": 10,
@@ -213,11 +228,22 @@
                 "style": {},
             },
         },
-        {"id": 3, "type": "tabs", "props": {"visible": True, "style": {}}},
+        {
+            "id": 3,
+            "type": "tabs",
+            "props": {
+                "visible": True,
+                "style": {},
+            },
+        },
         {
             "id": 444,
             "type": "tabitem",
-            "props": {"label": "X-ray", "visible": True, "style": {}},
+            "props": {
+                "label": "X-ray",
+                "visible": True,
+                "style": {},
+            },
         },
         {
             "id": 5,
@@ -262,7 +288,11 @@
         {
             "id": 9,
             "type": "tabitem",
-            "props": {"label": "CT Scan", "visible": True, "style": {}},
+            "props": {
+                "label": "CT Scan",
+                "visible": True,
+                "style": {},
+            },
         },
         {
             "id": 10,
@@ -410,7 +440,10 @@
         {
             "id": 3,
             "type": "tabs",
-            "props": {"style": {}, "value": True},
+            "props": {
+                "style": {},
+                "value": True,
+            },
         },
         {
             "id": 4,