From f74e403299d4bbc66de16db0813415c440a21b98 Mon Sep 17 00:00:00 2001
From: snowbitx <109521682+snowbitx@users.noreply.github.com>
Date: Thu, 4 Sep 2025 17:26:57 +0800
Subject: [PATCH] feat(components): [radio-group] support options prop
rendering (#21543)
* feat(components): [radio-group] support options
* test: add test case
* docs: tweak doc
* Update basic-usage.vue
* docs: tweak doc
* Update packages/components/radio/src/radio-group.vue
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
* chore: label prop
* Update packages/components/radio/src/radio-group.ts
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
* Update packages/components/radio/src/radio-group.vue
Co-authored-by: btea <2356281422@qq.com>
* refactor: use ts logic
* Update form.md
* Update message.ts
* refactor: fix effect lost
* refactor: use template logic and update version
* Update radio-group.ts
* Update options.vue
* Update options.vue
* refactor: rename props and support additional attributes and render
* chore: default key
* chore: use optionProps
* chore: fix build error
* chore: fix build error
* chore: fix build error
* chore: fix build error
* chore: fix build error
* Update radio-group.vue
* Update basic.vue
* refactor: refer checkbox
* Update pnpm-lock.yaml
* Update pnpm-workspace.yaml
* Update package.json
* Update package.json
* chore: ts error
* Update radio-group.ts
* Update radio-group.ts
* refactor: add more attr support and update version
* refactor: props consistent with select
* Update radio.md
* Update packages/components/radio/src/radio-group.vue
Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
* Update packages/components/radio/src/radio-group.vue
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
* chore: type with single line
* chore: fix build error
* chore: delete radioRenderer testcase
---------
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
Co-authored-by: btea <2356281422@qq.com>
Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
---
docs/en-US/component/radio.md | 34 +++++----
docs/examples/radio/options.vue | 24 +++++++
.../components/radio/__tests__/radio.test.tsx | 71 +++++++++++++++++++
packages/components/radio/src/radio-group.ts | 23 +++++-
packages/components/radio/src/radio-group.vue | 28 +++++++-
5 files changed, 165 insertions(+), 15 deletions(-)
create mode 100644 docs/examples/radio/options.vue
diff --git a/docs/en-US/component/radio.md b/docs/en-US/component/radio.md
index b5082544b6..bcd13cf266 100644
--- a/docs/en-US/component/radio.md
+++ b/docs/en-US/component/radio.md
@@ -61,6 +61,14 @@ radio/radio-button-group
:::
+## Options attribute ^(2.11.2)
+
+:::demo Shortcut from basic `el-radio-group` usage. You can customize the alias of the `options` through the `props` attribute.
+
+radio/options
+
+:::
+
## Button style
Radio with button styles.
@@ -119,18 +127,20 @@ radio/with-borders
### RadioGroup Attributes
-| Name | Description | Type | Default |
-| --------------------------- | ------------------------------------------------- | ---------------------------------- | ------- |
-| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] | — |
-| size | the size of radio buttons or bordered radios | ^[string] | default |
-| disabled | whether the nesting radios are disabled | ^[boolean] | false |
-| validate-event | whether to trigger form validation | ^[boolean] | true |
-| text-color | font color when button is active | ^[string] | #ffffff |
-| fill | border and background color when button is active | ^[string] | #409eff |
-| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in RadioGroup | ^[string] | — |
-| name | native `name` attribute | ^[string] | — |
-| id | native `id` attribute | ^[string] | — |
-| label ^(a11y) ^(deprecated) | same as `aria-label` in RadioGroup | ^[string] | — |
+| Name | Description | Type | Default |
+| --------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------- |
+| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] | — |
+| size | the size of radio buttons or bordered radios | ^[string] | default |
+| disabled | whether the nesting radios are disabled | ^[boolean] | false |
+| validate-event | whether to trigger form validation | ^[boolean] | true |
+| text-color | font color when button is active | ^[string] | #ffffff |
+| fill | border and background color when button is active | ^[string] | #409eff |
+| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in RadioGroup | ^[string] | — |
+| name | native `name` attribute | ^[string] | — |
+| id | native `id` attribute | ^[string] | — |
+| label ^(a11y) ^(deprecated) | same as `aria-label` in RadioGroup | ^[string] | — |
+| options ^(2.11.2) | data of the options, the key of `value` and `label` and `disabled` can be customize by `props` | ^[array]`Array<{[key: string]: any}>` | — |
+| props ^(2.11.2) | configuration options | ^[object]`{ value?: string, label?: string, disabled?: boolean}` |
### RadioGroup Events
diff --git a/docs/examples/radio/options.vue b/docs/examples/radio/options.vue
new file mode 100644
index 0000000000..e834334468
--- /dev/null
+++ b/docs/examples/radio/options.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/packages/components/radio/__tests__/radio.test.tsx b/packages/components/radio/__tests__/radio.test.tsx
index 3392a87d7c..f9ae4f14ac 100644
--- a/packages/components/radio/__tests__/radio.test.tsx
+++ b/packages/components/radio/__tests__/radio.test.tsx
@@ -205,6 +205,77 @@ describe('Radio group', () => {
expect(radio.value).toEqual(3)
expect(radio1.classes()).toContain('is-active')
})
+
+ it('renders el-radio-group using default option fields', async () => {
+ const radio = ref(3)
+ const options = [
+ {
+ value: 3,
+ label: 'Option A',
+ },
+ {
+ value: 6,
+ label: 'Option B',
+ },
+ {
+ value: 9,
+ label: 'Option C',
+ },
+ ]
+ const wrapper = mount(() => (
+
+ ))
+ await nextTick()
+ const [radio1, radio2] = wrapper.findAll('.el-radio')
+ expect(radio1.classes()).toContain('is-checked')
+ await radio2.trigger('click')
+ expect(radio2.classes()).toContain('is-checked')
+ expect(radio.value).toEqual(6)
+ })
+
+ it('renders el-radio-group with custom option fields', async () => {
+ const radio = ref(3)
+ const options = [
+ {
+ id: 3,
+ label: 'Option A',
+ },
+ {
+ id: 6,
+ label: 'Option B',
+ },
+ {
+ id: 9,
+ label: 'Option C',
+ },
+ ]
+ const wrapper = mount(() => (
+
+ ))
+ await nextTick()
+ const [radio1, radio2] = wrapper.findAll('.el-radio')
+ expect(radio1.classes()).toContain('is-checked')
+ await radio2.trigger('click')
+ expect(radio2.classes()).toContain('is-checked')
+ expect(radio.value).toEqual(6)
+ })
+
+ it('passes custom attributes from options to el-radio', () => {
+ const options = [
+ { value: 'a', label: 'A', 'data-test': 'custom-attr-1' },
+ { value: 'b', label: 'B', 'data-test': 'custom-attr-2' },
+ ]
+ const wrapper = mount(RadioGroup, {
+ props: { options },
+ })
+ const [radio1, radio2] = wrapper.findAll('.el-radio')
+ expect(radio1.attributes('data-test')).toBe('custom-attr-1')
+ expect(radio2.attributes('data-test')).toBe('custom-attr-2')
+ })
})
describe('Radio Button', () => {
diff --git a/packages/components/radio/src/radio-group.ts b/packages/components/radio/src/radio-group.ts
index 990de4b298..26260bf514 100644
--- a/packages/components/radio/src/radio-group.ts
+++ b/packages/components/radio/src/radio-group.ts
@@ -1,7 +1,8 @@
-import { buildProps } from '@element-plus/utils'
+import { buildProps, definePropType } from '@element-plus/utils'
import { useAriaProps, useSizeProp } from '@element-plus/hooks'
import { radioEmits } from './radio'
+import type { RadioPropsPublic } from './radio'
import type { ExtractPropTypes, __ExtractPublicPropTypes } from 'vue'
import type RadioGroup from './radio-group.vue'
@@ -56,6 +57,13 @@ export const radioGroupProps = buildProps({
type: Boolean,
default: true,
},
+ options: {
+ type: definePropType(Array),
+ },
+ props: {
+ type: definePropType(Object),
+ default: () => radioDefaultProps,
+ },
...useAriaProps(['ariaLabel']),
} as const)
export type RadioGroupProps = ExtractPropTypes
@@ -66,3 +74,16 @@ export type RadioGroupPropsPublic = __ExtractPublicPropTypes<
export const radioGroupEmits = radioEmits
export type RadioGroupEmits = typeof radioGroupEmits
export type RadioGroupInstance = InstanceType & unknown
+
+export type radioOption = RadioPropsPublic & Record
+
+export const radioDefaultProps: Required = {
+ label: 'label',
+ value: 'value',
+ disabled: 'disabled',
+}
+export type radioOptionProp = {
+ value?: string
+ label?: string
+ disabled?: string
+}
diff --git a/packages/components/radio/src/radio-group.vue b/packages/components/radio/src/radio-group.vue
index 8f3f1aef9a..731dad6f46 100644
--- a/packages/components/radio/src/radio-group.vue
+++ b/packages/components/radio/src/radio-group.vue
@@ -7,7 +7,13 @@
:aria-label="!isLabeledByFormItem ? ariaLabel || 'radio-group' : undefined"
:aria-labelledby="isLabeledByFormItem ? formItem!.labelId : undefined"
>
-
+
+
+
@@ -26,9 +32,14 @@ import { useFormItem, useFormItemInputId } from '@element-plus/components/form'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { useId, useNamespace } from '@element-plus/hooks'
import { debugWarn } from '@element-plus/utils'
-import { radioGroupEmits, radioGroupProps } from './radio-group'
+import {
+ radioDefaultProps,
+ radioGroupEmits,
+ radioGroupProps,
+} from './radio-group'
import { radioGroupKey } from './constants'
import { isEqual } from 'lodash-unified'
+import ElRadio from './radio.vue'
import type { RadioGroupProps } from './radio-group'
@@ -65,6 +76,19 @@ const name = computed(() => {
return props.name || radioId.value
})
+const aliasProps = computed(() => ({
+ ...radioDefaultProps,
+ ...props.props,
+}))
+const getOptionProps = (option: Record) => {
+ const base = {
+ label: option[aliasProps.value.label],
+ value: option[aliasProps.value.value],
+ disabled: option[aliasProps.value.disabled],
+ }
+ return { ...option, ...base }
+}
+
provide(
radioGroupKey,
reactive({