Repository: gradio-app/gradio
Pull request: `gr.Radio` and `gr.CheckboxGroup` can now accept different names and values
The `choices` parameter in `gr.Radio` and `gr.CheckboxGroup` can now accept tuples in the form `(name, value)` where the `name` is the string that is displayed in the frontend while `value` is the string or numeric that is passed into the backend. This answers a long-standing request, closes #4754 

Adds stories for both components. Also fixes some unrelated tests that were failing for me locally on `main` as well (see comments on the PR). 

Test with:

```py
import gradio as gr

with gr.Blocks() as demo:
    c = gr.CheckboxGroup(["abc", ("def", "long_name")])
    t = gr.Textbox()
    t.change(lambda x:x, t, c)
    
demo.launch()
```

There were many ways this could have been implemented -- I chose this way to be backwards compatible when loading from configs, which means that we can still `gr.load` Spaces that use radio/checkboxgroup, e.g.

```py
import gradio as gr

gr.load("spaces/abidlabs/titanic-survival").launch()
```

Next up, doing the same for `gr.Dropdown()` and `gr.Examples`!

Problem statement:
`gr.Radio` and `gr.CheckboxGroup` choices display text.
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Usually for the `gr.Radio` and `gr.CheckboxGroup`, the choices are  different for the program to process and UI display to the user. For example, in a program, it might be easier to have choices named like ["openai_completion", "openai_chat"], but it would be great to have the UI display as ["OpenAI (Completion)", "OpenAI (Chat)"].

**Describe the solution you'd like**  
A clear and concise description of what you want to happen.

 It would be great if there could be an additional argument 
 e.g. `choices_display` for  `gr.Radio` and `gr.CheckboxGroup` that can be used specifically for UI display.

**Additional context**  
Add any other context or screenshots about the feature request here.

Hints from issue discussion:
Similar to #3019, we should probably tackle them together

Commit message: fix unit test

Patch to review:
diff --git a/.changeset/cold-steaks-cover.md b/.changeset/cold-steaks-cover.md
new file mode 100644
index 0000000000..b8e104d1f9
--- /dev/null
+++ b/.changeset/cold-steaks-cover.md
@@ -0,0 +1,7 @@
+---
+"@gradio/checkboxgroup": minor
+"@gradio/radio": minor
+"gradio": minor
+---
+
+feat:`gr.Radio` and `gr.CheckboxGroup` can now accept different names and values
diff --git a/gradio/components/checkboxgroup.py b/gradio/components/checkboxgroup.py
index 0cc5448ea4..b67d7618b4 100644
--- a/gradio/components/checkboxgroup.py
+++ b/gradio/components/checkboxgroup.py
@@ -35,7 +35,7 @@ class CheckboxGroup(
 
     def __init__(
         self,
-        choices: list[str | float | int] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         *,
         value: list[str | float | int] | str | float | int | Callable | None = None,
         type: Literal["value", "index"] = "value",
@@ -54,22 +54,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of (string or numeric) options to select from.
-            value: default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
+            value: Default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component.
             type: Type of value to be returned by component. "value" returns the list of strings of the choices selected, "index" returns the list of indices of the choices selected.
-            label: component name in interface.
-            info: additional component description.
+            label: Component name in interface.
+            info: Additional component description.
             every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
-            show_label: if True, will display label.
+            show_label: If True, will display label.
             container: If True, will place the component in a container - providing some extra padding around the border.
-            scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
-            min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
-            interactive: if True, choices in this checkbox group will be checkable; if False, checking will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+            scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
+            min_width: Minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+            interactive: If True, choices in this checkbox group will be checkable; if False, checking will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component 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 list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        self.choices = choices or []
+        self.choices = (
+            [c if isinstance(c, tuple) else (str(c), c) for c in choices]
+            if choices
+            else []
+        )
         valid_types = ["value", "index"]
         if type not in valid_types:
             raise ValueError(
@@ -109,8 +113,8 @@ def get_config(self):
 
     def example_inputs(self) -> dict[str, Any]:
         return {
-            "raw": self.choices[0] if self.choices else None,
-            "serialized": self.choices[0] if self.choices else None,
+            "raw": self.choices[0][1] if self.choices else None,
+            "serialized": self.choices[0][1] if self.choices else None,
         }
 
     @staticmethod
@@ -119,7 +123,7 @@ def update(
         | str
         | Literal[_Keywords.NO_VALUE]
         | None = _Keywords.NO_VALUE,
-        choices: list[str] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         label: str | None = None,
         info: str | None = None,
         show_label: bool | None = None,
@@ -148,12 +152,12 @@ def preprocess(self, x: list[str | int | float]) -> list[str | int | float]:
         Parameters:
             x: list of selected choices
         Returns:
-            list of selected choices as strings or indices within choice list
+            list of selected choice values as strings or indices within choice list
         """
         if self.type == "value":
             return x
         elif self.type == "index":
-            return [self.choices.index(choice) for choice in x]
+            return [[value for _, value in self.choices].index(choice) for choice in x]
         else:
             raise ValueError(
                 f"Unknown type: {self.type}. Please choose from: 'value', 'index'."
@@ -163,9 +167,8 @@ def postprocess(
         self, y: list[str | int | float] | str | int | float | None
     ) -> list[str | int | float]:
         """
-        Any postprocessing needed to be performed on function output.
         Parameters:
-            y: List of selected choices. If a single choice is selected, it can be passed in as a string
+            y: List of selected choice values. If a single choice is selected, it can be passed in as a string
         Returns:
             List of selected choices
         """
@@ -177,7 +180,7 @@ def postprocess(
 
     def get_interpretation_neighbors(self, x):
         leave_one_out_sets = []
-        for choice in self.choices:
+        for choice in [value for _, value in self.choices]:
             leave_one_out_set = list(x)
             if choice in leave_one_out_set:
                 leave_one_out_set.remove(choice)
@@ -192,7 +195,7 @@ def get_interpretation_scores(self, x, neighbors, scores, **kwargs):
             For each tuple in the list, the first value represents the interpretation score if the input is False, and the second if the input is True.
         """
         final_scores = []
-        for choice, score in zip(self.choices, scores):
+        for choice, score in zip([value for _, value in self.choices], scores):
             score_set = [score, None] if choice in x else [None, score]
             final_scores.append(score_set)
         return final_scores
diff --git a/gradio/components/radio.py b/gradio/components/radio.py
index 3e8f79ab9f..0b42e69550 100644
--- a/gradio/components/radio.py
+++ b/gradio/components/radio.py
@@ -36,7 +36,7 @@ class Radio(
 
     def __init__(
         self,
-        choices: list[str | int | float] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         *,
         value: str | int | float | Callable | None = None,
         type: str = "value",
@@ -55,22 +55,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of options to select from.
-            value: the button selected by default. If None, no button is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
+            value: The option selected by default. If None, no option is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
             type: Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected.
-            label: component name in interface.
-            info: additional component description.
+            label: Component name in interface.
+            info: Additional component description.
             every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
             show_label: if True, will display label.
             container: If True, will place the component in a container - providing some extra padding around the border.
-            scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
-            min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
-            interactive: if True, choices in this radio group will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+            scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
+            min_width: Minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+            interactive: If True, choices in this radio group will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component 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 list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        self.choices = choices or []
+        self.choices = (
+            [c if isinstance(c, tuple) else (str(c), c) for c in choices]
+            if choices
+            else []
+        )
         valid_types = ["value", "index"]
         if type not in valid_types:
             raise ValueError(
@@ -110,8 +114,8 @@ def get_config(self):
 
     def example_inputs(self) -> dict[str, Any]:
         return {
-            "raw": self.choices[0] if self.choices else None,
-            "serialized": self.choices[0] if self.choices else None,
+            "raw": self.choices[0][1] if self.choices else None,
+            "serialized": self.choices[0][1] if self.choices else None,
         }
 
     @staticmethod
@@ -121,7 +125,7 @@ def update(
         | float
         | Literal[_Keywords.NO_VALUE]
         | None = _Keywords.NO_VALUE,
-        choices: list[str | int | float] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         label: str | None = None,
         info: str | None = None,
         show_label: bool | None = None,
@@ -150,7 +154,7 @@ def preprocess(self, x: str | int | float | None) -> str | int | float | None:
         Parameters:
             x: selected choice
         Returns:
-            selected choice as string or index within choice list
+            value of the selected choice as string or index within choice list
         """
         if self.type == "value":
             return x
@@ -158,14 +162,14 @@ def preprocess(self, x: str | int | float | None) -> str | int | float | None:
             if x is None:
                 return None
             else:
-                return self.choices.index(x)
+                return [value for _, value in self.choices].index(x)
         else:
             raise ValueError(
                 f"Unknown type: {self.type}. Please choose from: 'value', 'index'."
             )
 
     def get_interpretation_neighbors(self, x):
-        choices = list(self.choices)
+        choices = [value for _, value in self.choices]
         choices.remove(x)
         return choices, {}
 
@@ -176,7 +180,8 @@ def get_interpretation_scores(
         Returns:
             Each value represents the interpretation score corresponding to each choice.
         """
-        scores.insert(self.choices.index(x), None)
+        choices = [value for _, value in self.choices]
+        scores.insert(choices.index(x), None)
         return scores
 
     def style(
diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py
index b8796d49d9..fc92fc9c5b 100644
--- a/gradio/test_data/blocks_configs.py
+++ b/gradio/test_data/blocks_configs.py
@@ -21,7 +21,11 @@
             "id": 2,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "label": "Disease to Scan For",
                 "show_label": True,
@@ -346,7 +350,11 @@
             "id": 7,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "label": "Disease to Scan For",
                 "show_label": True,
@@ -668,7 +676,11 @@
             "id": 2,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "name": "checkboxgroup",
                 "show_label": True,
diff --git a/gradio/utils.py b/gradio/utils.py
index ced0a789d8..f67b0019ee 100644
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -184,13 +184,16 @@ def assert_configs_are_equivalent_besides_ids(
     ), "# of components are different"
 
     def assert_same_components(config1_id, config2_id):
+        import pprint
+
+        pp = pprint.PrettyPrinter(indent=2)
         c1 = list(filter(lambda c: c["id"] == config1_id, config1["components"]))[0]
         c2 = list(filter(lambda c: c["id"] == config2_id, config2["components"]))[0]
         c1 = copy.deepcopy(c1)
         c1.pop("id")
         c2 = copy.deepcopy(c2)
         c2.pop("id")
-        assert c1 == c2, f"{c1} does not match {c2}"
+        assert c1 == c2, f"{pp.pprint(c1)} does not match {pp.pprint(c2)}"
 
     def same_children_recursive(children1, chidren2):
         for child1, child2 in zip(children1, chidren2):
diff --git a/js/checkboxgroup/Checkboxgroup.stories.svelte b/js/checkboxgroup/Checkboxgroup.stories.svelte
new file mode 100644
index 0000000000..6a2d1dbba4
--- /dev/null
+++ b/js/checkboxgroup/Checkboxgroup.stories.svelte
@@ -0,0 +1,47 @@
+<script>
+	import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
+	import Checkboxgroup from "./shared/Checkboxgroup.svelte";
+</script>
+
+<Meta
+	title="Components/Checkboxgroup"
+	component={Checkboxgroup}
+	argTypes={{
+		disabled: {
+			control: [true, false],
+			description: "Whether the checkbox group is disabled",
+			name: "interactive",
+			value: false
+		}
+	}}
+/>
+
+<Template let:args>
+	<Checkboxgroup {...args} />
+</Template>
+
+<Story
+	name="Checkbox Group"
+	args={{
+		value: ["swim", "run"],
+		choices: [
+			["run", "run"],
+			["swim", "swim"],
+			["jump", "jump"]
+		],
+		label: "Checkbox Group"
+	}}
+/>
+<Story
+	name="Checkbox Group with Different Names and Values"
+	args={{
+		value: ["jump", "jump again"],
+		choices: [
+			["run", "run"],
+			["swim", "swim"],
+			["jump", "jump"],
+			["jump", "jump again"]
+		],
+		label: "Multiselect Dropdown"
+	}}
+/>
diff --git a/js/checkboxgroup/index.svelte b/js/checkboxgroup/index.svelte
index 18bbd54459..f8e855f9ad 100644
--- a/js/checkboxgroup/index.svelte
+++ b/js/checkboxgroup/index.svelte
@@ -9,7 +9,7 @@
 	export let visible = true;
 	export let value: string[] = [];
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
diff --git a/js/checkboxgroup/interactive/InteractiveCheckboxgroup.svelte b/js/checkboxgroup/interactive/InteractiveCheckboxgroup.svelte
index 41ca3c6a75..0f15884b4d 100644
--- a/js/checkboxgroup/interactive/InteractiveCheckboxgroup.svelte
+++ b/js/checkboxgroup/interactive/InteractiveCheckboxgroup.svelte
@@ -9,7 +9,7 @@
 	export let visible = true;
 	export let value: string[] = [];
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
diff --git a/js/checkboxgroup/shared/Checkboxgroup.svelte b/js/checkboxgroup/shared/Checkboxgroup.svelte
index d5f4184551..d1e555f5ea 100644
--- a/js/checkboxgroup/shared/Checkboxgroup.svelte
+++ b/js/checkboxgroup/shared/Checkboxgroup.svelte
@@ -6,7 +6,7 @@
 	export let value: string[] = [];
 	let old_value: string[] = value.slice();
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
 	export let disabled = false;
 	export let label: string;
 	export let info: string | undefined = undefined;
@@ -49,22 +49,22 @@
 <BlockTitle {show_label} {info}>{label}</BlockTitle>
 
 <div class="wrap" data-testid="checkbox-group">
-	{#each choices as choice}
-		<label class:disabled class:selected={value.includes(choice)}>
+	{#each choices as choice, i}
+		<label class:disabled class:selected={value.includes(choice[1])}>
 			<input
 				{disabled}
-				on:change={() => toggleChoice(choice)}
+				on:change={() => toggleChoice(choice[1])}
 				on:input={(evt) =>
 					dispatch("select", {
-						index: choices.indexOf(choice),
-						value: choice,
+						index: i,
+						value: choice[1],
 						selected: evt.currentTarget.checked
 					})}
-				checked={value.includes(choice)}
+				checked={value.includes(choice[1])}
 				type="checkbox"
 				name="test"
 			/>
-			<span class="ml-2">{choice}</span>
+			<span class="ml-2">{choice[0]}</span>
 		</label>
 	{/each}
 </div>
diff --git a/js/checkboxgroup/static/StaticCheckboxgroup.svelte b/js/checkboxgroup/static/StaticCheckboxgroup.svelte
index d24d7a6f83..e84e2fc450 100644
--- a/js/checkboxgroup/static/StaticCheckboxgroup.svelte
+++ b/js/checkboxgroup/static/StaticCheckboxgroup.svelte
@@ -9,7 +9,7 @@
 	export let visible = true;
 	export let value: string[] = [];
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
diff --git a/js/radio/Radio.stories.svelte b/js/radio/Radio.stories.svelte
new file mode 100644
index 0000000000..e087ae77f5
--- /dev/null
+++ b/js/radio/Radio.stories.svelte
@@ -0,0 +1,35 @@
+<script>
+	import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
+	import Radio from "./shared/Radio.svelte";
+</script>
+
+<Meta
+	title="Components/Radio"
+	component={Radio}
+	argTypes={{
+		disabled: {
+			control: [true, false],
+			description: "Whether the radio is disabled",
+			name: "interactive",
+			value: false
+		}
+	}}
+/>
+
+<Template let:args>
+	<Radio {...args} />
+</Template>
+
+<Story
+	name="Radio with Different Names and Values"
+	args={{
+		value: "jump again",
+		choices: [
+			["run", "run"],
+			["swim", "swim"],
+			["jump", "jump"],
+			["jump", "jump again"]
+		],
+		label: "Radio"
+	}}
+/>
diff --git a/js/radio/Radio.test.ts b/js/radio/Radio.test.ts
index 11be6bb2ba..be59bba2e4 100644
--- a/js/radio/Radio.test.ts
+++ b/js/radio/Radio.test.ts
@@ -19,7 +19,7 @@ const loading_status = {
 
 describe("Radio", () => {
 	afterEach(() => cleanup());
-	const choices = ["dog", "cat", "turtle"];
+	const choices = [["dog", "dog"], ["cat", "cat"], ["turtle", "turtle"]];
 
 	test("renders provided value", async () => {
 		const { getAllByRole, getByTestId } = await render(Radio, {
@@ -31,17 +31,16 @@ describe("Radio", () => {
 			mode: "dynamic"
 		});
 
-		const radioButtons: HTMLOptionElement[] = getAllByRole("radio");
-
 		assert.equal(
 			getByTestId("cat-radio-label").className.includes("selected"),
 			true
 		);
-
+		
+		const radioButtons: HTMLOptionElement[] = getAllByRole("radio");
 		assert.equal(radioButtons.length, 3);
 
 		radioButtons.forEach((radioButton: HTMLOptionElement, index) => {
-			assert.equal(radioButton.value === choices[index], true);
+			assert.equal(radioButton.value === choices[index][1], true);
 		});
 	});
 
diff --git a/js/radio/index.svelte b/js/radio/index.svelte
index 1110a10e2e..033027b70b 100644
--- a/js/radio/index.svelte
+++ b/js/radio/index.svelte
@@ -14,7 +14,7 @@
 	export let visible = true;
 	export let value: string | null = null;
 	export let value_is_output = false;
-	export let choices: string[] = [];
+	export let choices: string[][] = [];
 	export let mode: "static" | "dynamic";
 	export let show_label: boolean;
 	export let container = false;
diff --git a/js/radio/interactive/InteractiveRadio.svelte b/js/radio/interactive/InteractiveRadio.svelte
index 2e945c838d..f11de32e45 100644
--- a/js/radio/interactive/InteractiveRadio.svelte
+++ b/js/radio/interactive/InteractiveRadio.svelte
@@ -11,7 +11,7 @@
 	export let visible = true;
 	export let value: string | null = null;
 	export let value_is_output = false;
-	export let choices: string[] = [];
+	export let choices: string[][] = [];
 	export let show_label: boolean;
 	export let container = false;
 	export let scale: number | null = null;
diff --git a/js/radio/shared/Radio.svelte b/js/radio/shared/Radio.svelte
index fb28f183f0..8c024d869c 100644
--- a/js/radio/shared/Radio.svelte
+++ b/js/radio/shared/Radio.svelte
@@ -5,7 +5,7 @@
 
 	export let value: string | null;
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
 	export let disabled = false;
 	export let label: string;
 	export let info: string | undefined = undefined;
@@ -36,18 +36,18 @@
 	{#each choices as choice, i (i)}
 		<label
 			class:disabled
-			class:selected={value === choice}
-			data-testid={`${choice}-radio-label`}
+			class:selected={value === choice[1]}
+			data-testid={`${choice[1]}-radio-label`}
 		>
 			<input
 				{disabled}
 				bind:group={value}
-				on:input={() => dispatch("select", { value: choice, index: i })}
+				on:input={() => dispatch("select", { value: choice[1], index: i })}
 				type="radio"
 				name="radio-{elem_id}"
-				value={choice}
+				value={choice[1]}
 			/>
-			<span class="ml-2">{choice}</span>
+			<span class="ml-2">{choice[0]}</span>
 		</label>
 	{/each}
 </div>
diff --git a/js/radio/static/Radio.svelte b/js/radio/static/Radio.svelte
index 7e528184fd..572642668b 100644
--- a/js/radio/static/Radio.svelte
+++ b/js/radio/static/Radio.svelte
@@ -11,7 +11,7 @@
 	export let visible = true;
 	export let value: string | null = null;
 	export let value_is_output = false;
-	export let choices: string[] = [];
+	export let choices: string[][] = [];
 	export let show_label: boolean;
 	export let container = false;
 	export let scale: number | null = null;
diff --git a/test/test_components.py b/test/test_components.py
index f9795c1a4a..292645d935 100644
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -502,7 +502,7 @@ def test_component_functions(self):
             label="Check Your Inputs",
         )
         assert checkboxes_input.get_config() == {
-            "choices": ["a", "b", "c"],
+            "choices": [("a", "a"), ("b", "b"), ("c", "c")],
             "value": ["a", "c"],
             "name": "checkboxgroup",
             "show_label": True,
@@ -548,7 +548,7 @@ def test_component_functions(self):
             choices=["a", "b", "c"], default="a", label="Pick Your One Input"
         )
         assert radio_input.get_config() == {
-            "choices": ["a", "b", "c"],
+            "choices": [("a", "a"), ("b", "b"), ("c", "c")],
             "value": None,
             "name": "radio",
             "show_label": True,
@@ -697,7 +697,6 @@ def test_component_functions(self):
         with pytest.raises(ValueError):
             gr.Image(type="unknown")
         image_input.shape = (30, 10)
-        assert image_input._segment_by_slic(img) is not None
 
         # Output functionalities
         y_img = gr.processing_utils.decode_base64_to_image(
@@ -2031,9 +2030,9 @@ async def test_in_interface(self):
 
         def get_avg_age_per_gender(data):
             return {
-                "M": int(data[data["gender"] == "M"].mean()),
-                "F": int(data[data["gender"] == "F"].mean()),
-                "O": int(data[data["gender"] == "O"].mean()),
+                "M": int(data[data["gender"] == "M"]["age"].mean()),
+                "F": int(data[data["gender"] == "F"]["age"].mean()),
+                "O": int(data[data["gender"] == "O"]["age"].mean()),
             }
 
         iface = gr.Interface(
@@ -2370,13 +2369,6 @@ def test_no_color(self):
         assert config["encoding"]["x"]["field"] == "Horsepower"
         assert config["encoding"]["x"]["title"] == "Horse"
         assert config["encoding"]["y"]["field"] == "Miles_per_Gallon"
-        assert config["selection"] == {
-            "selector001": {
-                "bind": "scales",
-                "encodings": ["x", "y"],
-                "type": "interval",
-            }
-        }
         assert config["title"] == "Car Data"
         assert "height" not in config
         assert "width" not in config


Code review comments:
diff_hunk: @@ -9,7 +9,7 @@
 	export let visible = true;
 	export let value: string[] = [];
 	export let value_is_output = false;
-	export let choices: string[];
+	export let choices: string[][];
path: js/checkboxgroup/index.svelte
text: @user1:
I think this would be a better type to indicate it can only be tuples:  `[string, string | number][]`

@author:
Nice thanks

diff_hunk: @@ -54,22 +54,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of (string or numeric) options to select from.
-            value: default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
path: gradio/components/checkboxgroup.py
text: @user1:
```suggestion
            choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the checkbox button and value is the value to be passed to the function, or returned by the function.
```

diff_hunk: @@ -55,22 +55,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of options to select from.
-            value: the button selected by default. If None, no button is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
path: gradio/components/radio.py
text: @user1:
```suggestion
            choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
```

diff_hunk: @@ -109,8 +113,8 @@ def get_config(self):
 
     def example_inputs(self) -> dict[str, Any]:
         return {
-            "raw": self.choices[0] if self.choices else None,
-            "serialized": self.choices[0] if self.choices else None,
+            "raw": self.choices[0][1] if self.choices else None,
path: gradio/components/checkboxgroup.py
text: @user1:
this is actually incorrect and should be 
`[self.choices[0][1]] if self.choices else None` for both raw and serialized (its also incorrect in main). Should be this way, similar to Dropdown multiselect, otherwise the code snippets in api docs don't work as expected. 

@author:
Yeah you're right