Skip to content

Bouton radio (et bouton radio riche) - DsfrRadioButton

🌟 Introduction

Important

Il est fortement déconseillé d’utiliser directement DsfrRadioButton, il est préférable d’utiliser DsfrRadioButtonSet.

Les boutons radio permettent à l’utilisateur de sélectionner une seule option dans une liste.

Le bouton radio ne peut pas être utilisé seul : il faut au minimum 2 options. Il est préférable de ne pas sélectionner d’option par défaut pour que le choix de l’utilisateur soit conscient (en particulier si le choix est obligatoire).

Les boutons radio riches permettent de sélectionner un choix parmi une liste d’options illustrées. À la différence du bouton radio simple, l’image permet d’illustrer et d’accompagner l’utilisateur dans son choix.

🏅 La documentation sur le bouton radio et le bouton radio riche sur le DSFR

La story sur le bouton radio sur le storybook de VueDsfr

📐 Structure

Le composant DsfrRadioButtonSet est composé des éléments suivants :

  • Un élément <div> englobant l'ensemble du groupe de radio.
  • Une légende (legend) définie par la prop legend et personnalisable avec le slot legend.
  • Un groupe de boutons radio individuels rendus par le composant DsfrRadioButton.
  • Un message d'information, d'erreur ou de validation, affiché en dessous du groupe de boutons radio (facultatif).

🛠️ Props

NomTypeDescriptionObligatoire
namestringNom du champ <input> associé à l'ensemble des boutons radio
modelValuestring ou number ou booleanValeur courante du composant (sélection courante)
inlinebooleanAffiche le bouton radio en ligne (par défaut : false)
idstringId du champ <input> (par défaut: undefined)
idstringId du champ <input> (par défaut: undefined)
smallbooleanAffiche les boutons radio en taille réduite (par défaut : false)

📡 Événements

DsfrRadioButtonSet émet l'événement suivant :

NomDescription
update:modelValueEst émis lorsque la valeur d'un bouton radio est sélectionnée

🧩 Slots

DsfrRadioButtonSet fournit les slots suivants pour la personnalisation :

  • legend : Permet de personnaliser le contenu de la légende.
  • required-tip : Permet d'ajouter un astérisque indiquant que le champ est obligatoire (fonctionne uniquement si l'attribut required est défini sur le composant).

🪆 Relation avec DsfrRadioButtonSet

Le composant DsfrRadioButtonSet utilise le composant DsfrRadioButton pour rendre visuellement chaque option du groupe. Chaque bouton radio individuel hérite des props du composant DsfrRadioButtonSet excepté modelValue.

📝 Exemples

vue
<script lang="ts" setup>
import { ref } from 'vue'

import DsfrRadioButtonSet from '../DsfrRadioButtonSet.vue'
import DsfrRadioButton from '../DsfrRadioButton.vue'

const modelValue1 = ref()
const options = [
  {
    label: 'Première valeur',
    id: 'name1-1',
    value: 'name1-1',
    hint: 'Description de la première valeur',
  },
  {
    label: 'Deuxième valeur',
    id: 'name1-2',
    value: 'name1-2',
    hint: 'Description de la deuxième valeur',
  },
  {
    label: 'Troisième valeur',
    id: 'name1-3',
    value: 'name1-3',
    hint: 'Description de la troisième valeur',
  },
]
</script>

<template>
  <div class="fr-container fr-my-2v">
    <DsfrRadioButtonSet>
      <DsfrRadioButton
        v-for="option of options"
        :key="option.id"
        v-model="modelValue1"
        name="name-1"
        v-bind="option"
      />
    </DsfrRadioButtonSet>
    <p>
      modelValue1: {{ modelValue1 }}
    </p>
  </div>
</template>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import { computed } from 'vue'

import DsfrRadioButton from './DsfrRadioButton.vue'
import { getRandomId } from '../../utils/random-utils'

import type { DsfrRadioButtonSetProps } from './DsfrRadioButton.types'

export type { DsfrRadioButtonSetProps }

const props = withDefaults(defineProps<DsfrRadioButtonSetProps>(), {
  titleId: () => getRandomId('radio-button', 'group'),
  errorMessage: '',
  validMessage: '',
  legend: '',
  options: () => [],
})

const emit = defineEmits<{ (e: 'update:modelValue', payload: string | number | boolean): void }>()

const message = computed(() => props.errorMessage || props.validMessage)
const additionalMessageClass = computed(() => props.errorMessage ? 'fr-error-text' : 'fr-valid-text')

const onChange = ($event: string) => {
  if ($event === props.modelValue) {
    return
  }
  emit('update:modelValue', $event)
}
</script>

<template>
  <div class="fr-form-group">
    <fieldset
      class="fr-fieldset"
      :class="{
        'fr-fieldset--error': errorMessage,
        'fr-fieldset--valid': validMessage,
      }"
      :disabled="disabled"
      :aria-labelledby="`${titleId} messages-${titleId}`"
      :role="(errorMessage || validMessage) ? 'group' : undefined"
    >
      <legend
        v-if="legend"
        :id="titleId"
        class="fr-fieldset__legend fr-fieldset__legend--regular"
      >
        <!-- @slot Slot pour personnaliser tout le contenu de la balise <legend> cf. [DsfrInput](/?path=/story/composants-champ-de-saisie-champ-simple-dsfrinput--champ-avec-label-personnalise). Une **props porte le même nom pour une légende simple** (texte sans mise en forme) -->
        <slot name="legend">
          {{ legend }}
          <!-- @slot Slot pour indiquer que le champ est obligatoire. Par défaut, met une astérisque si `required` est à true (dans un `<span class="required">`) -->
          <slot name="required-tip">
            <span
              v-if="required"
              class="required"
            >&nbsp;*</span>
          </slot>
        </slot>
      </legend>

      <slot>
        <DsfrRadioButton
          v-for="(option, i) of options"
          :key="typeof option.value === 'boolean' ? i : (option.value || i)"
          :name="name"
          :aria-disabled="option.disabled"
          v-bind="option"
          :small="small"
          :inline="inline"
          :model-value="modelValue"
          @update:model-value="onChange($event as string)"
        />
      </slot>

      <div
        v-if="message"
        :id="`messages-${titleId}`"
        class="fr-messages-group"
        aria-live="assertive"
      >
        <p
          class="fr-message--info  flex  items-center"
          :class="additionalMessageClass"
        >
          <span class="line-1">{{ message }}</span>
        </p>
      </div>
    </fieldset>
  </div>
</template>

<style>
.fr-fieldset--error .fr-radio-group .fr-label::before {
  box-shadow: inset 0 0 0 1px var(--border-plain-error), inset 0 0 0 12px var(--background-default-grey), inset 0 0 0 12px var(--border-plain-success)
}

.fr-fieldset--valid .fr-radio-group .fr-label::before {
  box-shadow: inset 0 0 0 1px var(--border-plain-success), inset 0 0 0 12px var(--background-default-grey), inset 0 0 0 12px var(--border-plain-success)
}
</style>
ts
export type DsfrRadioButtonProps = {
  id?: string
  name?: string
  modelValue: string | number | boolean | undefined
  disabled?: boolean
  small?: boolean
  inline?: boolean
  value: string | number | boolean
  label?: string
  hint?: string
  img?: string
}

export type DsfrRadioButtonOptions = (Omit<DsfrRadioButtonProps, 'modelValue'>)[]

export type DsfrRadioButtonSetProps = {
  titleId?: string
  disabled?: boolean
  required?: boolean
  small?: boolean
  inline?: boolean
  name?: string
  errorMessage?: string
  validMessage?: string
  legend?: string
  modelValue?: string | number | boolean | undefined
  options?: Omit<DsfrRadioButtonProps, 'modelValue'>[]
}