Radio Group
INFO
This page has not yet been updated to use the defineModel
macro, which was added in Vue 3.4. The techniques described here should still work, but in some cases it might be better to use defineModel
instead.
Radio Group Example
A radio group component acts as a wrapper around multiple radio components, with a single v-model
on the wrapper:
<template>
<basic-radio-group v-model="radioValue">
<basic-radio value="First" />
<basic-radio value="Second" />
<basic-radio value="Third" />
</basic-radio-group>
<pre>Bound value: {{ radioValue }}</pre>
</template>
<script setup>
import { ref } from 'vue'
import BasicRadio from './radio.vue'
import BasicRadioGroup from './radio-group.vue'
const radioValue = ref('First')
</script>
It would look something like this:
radio-group.vue
would use provide
to communicate with radio.vue
:
<template>
<div>
<slot />
</div>
</template>
<script setup>
import { computed, provide } from 'vue'
const props = defineProps({
modelValue: {
required: true
}
})
const emit = defineEmits(['update:modelValue'])
provide('radioModel', computed({
get () {
return props.modelValue
},
set (value) {
emit('update:modelValue', value)
}
}))
</script>
The radio.vue
here is specially written to use inject
instead of a modelValue
:
<template>
<div>
<input type="radio" v-model="radioModel" :value="value">
</div>
</template>
<script setup>
import { inject } from 'vue'
const props = defineProps({
value: {
required: true
}
})
const radioModel = inject('radioModel')
</script>
Support for direct use of v-model
with the radio
component has been omitted from this example. That technique is shown in the Radio example. The two can be combined by using a default value with inject
and then checking whether that value is set.
Vue Patterns
See Coupled Components with provide
/inject
Apart from the naming choices, there's nothing in this radio-group.vue
implementation that assumes the children are radio buttons. It could be made to work with checkboxes, toggle switches or any similar components that involve picking from a list of options.
It could be argued that injecting a computed
like this is violating one-way data flow for updates, or at least it gives that impression from the perspective of radio.vue
. You could inject a separate ref
and update function if you prefer.
Alternatives
Another way to implement a radio group would be to use a prop to pass the options rather than a slot. e.g.:
<template>
<basic-radio-group
v-model="radioValue"
:options="['First', 'Second', 'Third']"
/>
</template>
As the radio group is now responsible for creating the child components, it can use props and events for communicating with the children rather than provide
/inject
. This does simplify the implementation of the children, but it also forces the radio group to take on responsibility for laying out the radios. Whereas a slot allows other containers, dividers and text to be included along with the radios, the prop-based approach needs everything to be passed in via props. The slot-based approach could be seen as a better separation of concerns, though if you don't need the extra flexibility it may be unnecessary complexity.
Both approaches could be implemented in the same component.
Libraries
Various libraries include a radio component, including:
- Vuetify - Radio Group
- Element Plus - Radio Group
- Quasar - Option Group (uses a prop-based approach for the options)
- Ant Design Vue - Radio Group
- Headless UI - Radio Group
- Naive UI - Radio Group