Repository: gradio-app/gradio
Pull request: Attach `elem_classes` selectors to layout elements, and an id to the Tab button (for targeting via CSS/JS)
This PR does two things:

1. It passes the `elem_classes` attribute to the Blocks layouts (e.g. Row, Accordion, Tab, etc.) We had these attributes set up correctly in the frontend, but we were never passing them in from the frontend

2. For the `TabItem`, the `elem_id` would get attached to the div holding the tab contents. We now also attach an `{elem_id}-button` to the button that you click on to select the tab. This closes: #5585 

Test code:

```py
import gradio as gr

css = """
#blue-button{
    background-color:aqua
}
#green-button {
    background-color:green
}
#red{
    background-color:red
}
"""

with gr.Blocks(css=css) as demo:
    with gr.Tab(
            "Blue",
            elem_id="blue",
        ):
            gr.Textbox()
    with gr.Tab(
            "Green",
            elem_id="green",
        ):
            with gr.Accordion("Red background", elem_id="red"):
                gr.Textbox()
                gr.Textbox()
        
    demo.launch()
```

Problem statement:
Add id, class, and or attribute to a Tab's button
**Is your feature request related to a problem? Please describe.**  
I have Javascript that is added to the page which will activate a tab during specific instances. I do this by scanning the tab buttons in the tab bar and checking the label of each one to know which one to activate, however, an issue has popped up where someone is using a translation extension that swaps out the labels on the buttons and that breaks the functionality I was using to find the tab button.

**Describe the solution you'd like**  
If we were able to add an id, class, or attribute to the tab's button I could then use that to find the correct button instead of a string compare on the label.

Example: 
```python
with gr.Tab(
    my_tab_label,
    elem_id=f"my_tab_id",
    elem_classes="my_tab_class",
    btn_id=f"my_tabs_btn_id",
    btn_classes="my_tabs_btn_class"
):
```

Would result in:

```html
<div class="tabs gradio-tabs" id="tabs">
    <div class="tab-nav">
        <button id="my_tabs_btn_id" class="my_tabs_btn_class">my_tab_label</button>
    </div>
    <div id="my_tab_id" class="tabitem gradio-tabitem my_tab_class">
        <!-- tab content -->
    </div>
</div>
```

Hints from issue discussion:
I'm hesitant to add two separate parameters. However, what if we were to automatically set the btn id to be "{elem_id}-btn", allowing you to target it directly? 
that would be perfect

Commit message: add changeset

Patch to review:
diff --git a/.changeset/real-items-cover.md b/.changeset/real-items-cover.md
new file mode 100644
index 0000000000..b134d02c78
--- /dev/null
+++ b/.changeset/real-items-cover.md
@@ -0,0 +1,7 @@
+---
+"@gradio/tabitem": patch
+"@gradio/tabs": patch
+"gradio": patch
+---
+
+feat:Attach `elem_classes` selectors to layout elements, and an id to the Tab button (for targeting via CSS/JS)
diff --git a/gradio/layouts.py b/gradio/layouts.py
index c370651deb..3d9c9d2d32 100644
--- a/gradio/layouts.py
+++ b/gradio/layouts.py
@@ -34,6 +34,7 @@ def __init__(
         variant: Literal["default", "panel", "compact"] = "default",
         visible: bool = True,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         equal_height: bool = True,
         **kwargs,
     ):
@@ -42,13 +43,16 @@ def __init__(
             variant: row type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap).
             visible: If False, row will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
             equal_height: If True, makes every child element have equal height
         """
         self.variant = variant
         self.equal_height = equal_height
         if variant == "compact":
             self.allow_expected_parents = False
-        super().__init__(visible=visible, elem_id=elem_id, **kwargs)
+        super().__init__(
+            visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
 
     def get_config(self):
         return {
@@ -109,6 +113,7 @@ def __init__(
         variant: Literal["default", "panel", "compact"] = "default",
         visible: bool = True,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         **kwargs,
     ):
         """
@@ -118,6 +123,7 @@ def __init__(
             variant: column type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap).
             visible: If False, column will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
         if scale != round(scale):
             warn_deprecation(
@@ -129,7 +135,9 @@ def __init__(
         self.variant = variant
         if variant == "compact":
             self.allow_expected_parents = False
-        super().__init__(visible=visible, elem_id=elem_id, **kwargs)
+        super().__init__(
+            visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
 
     def get_config(self):
         return {
@@ -163,6 +171,7 @@ def __init__(
         selected: int | str | None = None,
         visible: bool = True,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         **kwargs,
     ):
         """
@@ -170,8 +179,11 @@ def __init__(
             selected: The currently selected tab. Must correspond to an id passed to the one of the child TabItems. Defaults to the first TabItem.
             visible: If False, Tabs will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        BlockContext.__init__(self, visible=visible, elem_id=elem_id, **kwargs)
+        BlockContext.__init__(
+            self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
         Changeable.__init__(self)
         Selectable.__init__(self)
         self.selected = selected
@@ -210,15 +222,19 @@ def __init__(
         *,
         id: int | str | None = None,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         **kwargs,
     ):
         """
         Parameters:
             label: The visual label for the tab
             id: An optional identifier for the tab, required if you wish to control the selected tab from a predict function.
-            elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_id: An optional string that is assigned as the id of the <div> containing the contents of the Tab layout. The same string followed by "-button" is attached to the Tab button. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        BlockContext.__init__(self, elem_id=elem_id, **kwargs)
+        BlockContext.__init__(
+            self, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
         Selectable.__init__(self)
         self.label = label
         self.id = id
@@ -256,14 +272,18 @@ def __init__(
         *,
         visible: bool = True,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         **kwargs,
     ):
         """
         Parameters:
             visible: If False, group will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        super().__init__(visible=visible, elem_id=elem_id, **kwargs)
+        super().__init__(
+            visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
 
     def get_config(self):
         return {"type": "group", **super().get_config()}
@@ -364,6 +384,7 @@ def __init__(
         open: bool = True,
         visible: bool = True,
         elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
         **kwargs,
     ):
         """
@@ -371,10 +392,13 @@ def __init__(
             label: name of accordion section.
             open: if True, accordion is open by default.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
         self.label = label
         self.open = open
-        super().__init__(visible=visible, elem_id=elem_id, **kwargs)
+        super().__init__(
+            visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs
+        )
 
     def get_config(self):
         return {
diff --git a/js/tabitem/static/TabItem.svelte b/js/tabitem/static/TabItem.svelte
index 84f08b22ac..0451cd688e 100644
--- a/js/tabitem/static/TabItem.svelte
+++ b/js/tabitem/static/TabItem.svelte
@@ -14,10 +14,10 @@
 	const { register_tab, unregister_tab, selected_tab, selected_tab_index } =
 		getContext(TABS) as any;
 
-	let tab_index = register_tab({ name, id });
+	let tab_index = register_tab({ name, id, elem_id });
 
 	onMount(() => {
-		return (): void => unregister_tab({ name, id });
+		return (): void => unregister_tab({ name, id, elem_id });
 	});
 
 	$: $selected_tab_index === tab_index &&
diff --git a/js/tabs/static/Tabs.svelte b/js/tabs/static/Tabs.svelte
index 653541fafe..76e813e303 100644
--- a/js/tabs/static/Tabs.svelte
+++ b/js/tabs/static/Tabs.svelte
@@ -10,6 +10,7 @@
 	interface Tab {
 		name: string;
 		id: object;
+		elem_id: string | undefined;
 	}
 
 	export let visible = true;
@@ -28,7 +29,7 @@
 
 	setContext(TABS, {
 		register_tab: (tab: Tab) => {
-			tabs.push({ name: tab.name, id: tab.id });
+			tabs.push({ name: tab.name, id: tab.id, elem_id: tab.elem_id });
 			selected_tab.update((current) => current ?? tab.id);
 			tabs = tabs;
 			return tabs.length - 1;
@@ -58,11 +59,15 @@
 	<div class="tab-nav scroll-hide">
 		{#each tabs as t, i (t.id)}
 			{#if t.id === $selected_tab}
-				<button class="selected">
+				<button
+					class="selected"
+					{...elem_id ? { id: t.elem_id + "-button" } : {}}
+				>
 					{t.name}
 				</button>
 			{:else}
 				<button
+					{...elem_id ? { id: t.elem_id + "-button" } : {}}
 					on:click={() => {
 						change_tab(t.id);
 						dispatch("select", { value: t.name, index: i });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c54729d91..55538157d0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
 
 settings:
   autoInstallPeers: true
@@ -6401,7 +6401,7 @@ packages:
     peerDependencies:
       '@sveltejs/kit': ^1.0.0
     dependencies:
-      '@sveltejs/kit': 1.16.3(svelte@3.57.0)(vite@4.3.5)
+      '@sveltejs/kit': 1.16.3(svelte@3.59.2)(vite@4.3.9)
       import-meta-resolve: 3.0.0
     dev: true
 


Code review comments:
diff_hunk: @@ -58,11 +59,15 @@
 	<div class="tab-nav scroll-hide">
 		{#each tabs as t, i (t.id)}
 			{#if t.id === $selected_tab}
-				<button class="selected">
+				<button
+					class="selected"
+					{...elem_id ? { id: t.elem_id + "-button" } : {}}
path: js/tabs/static/Tabs.svelte
text: @user1:
In Svelte if you set an attribute to null it gets removed. That might be a slightly cleaner way of expressing this. 

@author:
Ah nice. Figured there'd a nicer way. Will update