Skip to content

🌟 Introduction

Ce composant permet de gérer un ensemble de cases à cocher DSFR. Il est composé d'un libellé (legend), d'options individuelles représentées par le composant DsfrCheckbox, et d'un message d'information, d'erreur ou de validation global.

📐 Structure

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

  • Un élément <fieldset> contenant l'ensemble des cases à cocher
  • Une légende (legend) définie par la prop legend et personnalisable avec le slot legend
  • Un groupe de cases à cocher individuelles rendues par le composant DsfrCheckbox
  • Un message d'information, d'erreur ou de validation, affiché en dessous du groupe de cases à cocher

🛠️ Props

NomTypeDescriptionObligatoire
options(DsfrCheckboxProps & InputHTMLAttributes)[]Tableau d'options définissant les cases à cocher individuelles
modelValuestring[]Valeur courante du composant, un tableau de valeurs (propriété value de chaque option de la prop options) des cases cochées
disabledbooleanIndique si l'ensemble des cases à cocher est désactivé
errorMessagestringMessage d'erreur global à afficher
inlinebooleanAffiche les cases à cocher en ligne (par défaut : false)
legendstringTexte de la légende
requiredbooleanIndique si l'ensemble des cases à cocher est obligatoire
smallbooleanAffiche les cases à cocher en taille réduite
titleIdstringIdentifiant unique du champ (générée automatiquement si non fournie)
validMessagestringMessage de validation global à afficher

Attention

Avant la v7, le tableau modelValue était un tableau de string avec les valeurs des propriétés de l’attribut name de chaque case à cocher.

Ce n’était ni une API idéale, ni le comportement attendu en Vue natif ou en HTML/JS natif.

📡 Événements

DsfrCheckboxSet émet l'événement suivant :

NomDescription
update:modelValueEst émis lorsque la sélection des cases à cocher change

🧩 Slots

DsfrCheckboxSet fournit les slots suivants pour la personnalisation :

  • legend : Permet de personnaliser le contenu de la légende.
  • required-tip : Permet d'ajouter plus qu’un astérisque pour indiquer que le champ est obligatoire ou d’autres détails sur cette case à cocher.

🪆 Relation avec DsfrCheckbox

DsfrChecboxSet utilise en interne DsfrCheckbox, et permet de récupérer dans modelValue sous forme de tableau les valeurs de la prop value de chaque case à cocher qui est cochée.

Cf. les exemples ci-dessous

📝 Exemples

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

import DsfrCheckboxSet from '../DsfrCheckboxSet.vue'

const modelValue1 = ref([])
const modelValue2 = ref([])
const modelValue3 = ref([])
const modelValue4 = ref([])
const modelValue5 = ref([])
const modelValue6 = ref([])

const options1 = [
  {
    label: 'Première valeur',
    value: 'une chaîne en premier',
    id: 'name1-1',
    name: 'name1-1',
    hint: 'La première valeur est une chaîne de caractères',
  },
  {
    label: 'Deuxième valeur',
    value: 42,
    id: 'name1-2',
    name: 'name1-2',
    hint: 'La valeur est ici un chiffre',
  },
  {
    label: 'Troisième valeur',
    value: { foo: 'foo', bar: 42 },
    id: 'name1-3',
    name: 'name1-3',
    hint: 'Et ici la valeur est un littéral objet',
  },
]

const options2 = structuredClone(options1).map(option => Object.fromEntries(
  Object.entries(option).map(([key, value]) => [key, ['id', 'name'].includes(key) ? value.replace('name1', 'name2') : value]),
))

const options3 = structuredClone(options1).map(option => Object.fromEntries(
  Object.entries(option).map(([key, value]) => [key, ['id', 'name'].includes(key) ? value.replace('name1', 'name3') : value]),
))

const options4 = structuredClone(options1).map(option => Object.fromEntries(
  Object.entries(option).filter(([key]) => key !== 'hint').map(([key, value]) => [key, ['id', 'name'].includes(key) ? value.replace('name1', 'name4') : value]),
))

const options5 = structuredClone(options1).map(option => Object.fromEntries(
  Object.entries(option).filter(([key]) => key !== 'hint').map(([key, value]) => [key, ['id', 'name'].includes(key) ? value.replace('name1', 'name5') : value]),
))

const options6 = structuredClone(options1).map(option => Object.fromEntries(
  Object.entries(option).filter(([key]) => key !== 'hint').map(([key, value]) => [key, ['id', 'name'].includes(key) ? value.replace('name1', 'name6') : value]),
))

const errorMessage = 'Message d’erreur'
const validMessage = 'Message de validation'
</script>

<template>
  <div class="fr-container fr-my-2v">
    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue1"
        legend="Groupe de cases à cocher simple"
        :options="options1"
      />
      <p>
        modelValue1: {{ modelValue1 }}
      </p>
    </div>

    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue2"
        legend="Groupe de cases à cocher avec erreur"
        :options="options2"
        :error-message="errorMessage"
      />
      <p>
        modelValue2: {{ modelValue2 }}
      </p>
    </div>

    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue3"
        legend="Groupe de cases à cocher avec message de validation"
        :options="options3"
        :valid-message="validMessage"
      />
      <p>
        modelValue3: {{ modelValue3 }}
      </p>
    </div>

    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue4"
        legend="Groupe de cases à cocher en ligne"
        :options="options4"
        inline
      />
      <p>
        modelValue4: {{ modelValue4 }}
      </p>
    </div>

    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue5"
        legend="Groupe de cases à cocher en ligne avec erreur"
        :options="options5"
        inline
        error-message="Message d’erreur"
      />
      <p>
        modelValue5: {{ modelValue5 }}
      </p>
    </div>

    <div class="fr-my-2v">
      <DsfrCheckboxSet
        v-model="modelValue6"
        legend="Groupe de cases à cocher en ligne avec erreur"
        :options="options6"
        inline
        valid-message="Message de validation"
      />
      <p>
        modelValue6: {{ modelValue6 }}
      </p>
    </div>
  </div>
</template>

⚙️ Code source du composant

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

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

import DsfrCheckbox from './DsfrCheckbox.vue'
import type { DsfrCheckboxSetProps } from './DsfrCheckbox.types'

export type { DsfrCheckboxSetProps }
</script>

<script lang="ts" setup>
const props = withDefaults(defineProps<DsfrCheckboxSetProps>(), {
  titleId: () => getRandomId('checkbox', 'group'),
  errorMessage: '',
  validMessage: '',
  legend: '',
  options: () => [],
  modelValue: () => [],
})

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

const ariaLabelledby = computed(() => message.value ? `${props.titleId} messages-${props.titleId}` : props.titleId)

const modelValue = defineModel()
</script>

<template>
  <div class="fr-form-group">
    <fieldset
      class="fr-fieldset"
      :class="{
        'fr-fieldset--error': errorMessage,
        'fr-fieldset--valid': !errorMessage && validMessage,
      }"
      :disabled="disabled"
      :aria-labelledby="ariaLabelledby"
      :aria-invalid="ariaInvalid"
      :role="(errorMessage || validMessage) ? 'group' : undefined"
    >
      <legend
        :id="titleId"
        class="fr-fieldset__legend fr-text--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 **rte 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>
        <DsfrCheckbox
          v-for="option in options"
          :id="option.id"
          :key="option.id || option.name"
          v-model="modelValue"
          :value="option.value"
          :name="option.name"
          :label="option.label"
          :disabled="option.disabled"
          :aria-disabled="option.disabled"
          :small="small"
          :inline="inline"
          :hint="option.hint"
        />
      </slot>
      <div
        v-if="message"
        :id="`messages-${titleId}`"
        class="fr-messages-group"
        role="alert"
      >
        <p
          class="fr-message--info  flex  items-center"
          :class="additionalMessageClass"
        >
          <span>{{ message }}</span>
        </p>
      </div>
    </fieldset>
  </div>
</template>
ts
import type { InputHTMLAttributes } from 'vue'

export type DsfrCheckboxProps = {
  id?: string
  name: string
  required?: boolean
  value: unknown
  checked?: boolean
  modelValue: Array<unknown>
  small?: boolean
  inline?: boolean
  label?: string
  errorMessage?: string
  validMessage?: string
  hint?: string
}

export type DsfrCheckboxSetProps = {
  titleId?: string
  disabled?: boolean
  inline?: boolean
  required?: boolean
  small?: boolean
  errorMessage?: string
  validMessage?: string
  legend?: string
  options?: (DsfrCheckboxProps & InputHTMLAttributes)[]
  modelValue?: Array<unknown>
  ariaInvalid?: boolean
}