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 noms 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

📡 É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 non pas les états de chaque case à cocher, mais un tableau de string contenant les valeurs de la prop name 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(undefined)
const modelValue2 = ref(undefined)
const modelValue3 = ref(undefined)
const modelValue4 = ref(undefined)
const modelValue5 = ref(undefined)
const modelValue6 = ref(undefined)

const options1 = [
  {
    label: 'Première valeur',
    id: 'name1-1',
    name: 'name1-1',
    hint: 'Description de la première valeur',
  },
  {
    label: 'Deuxième valeur',
    id: 'name1-2',
    name: 'name1-2',
    hint: 'Description de la deuxième valeur',
  },
  {
    label: 'Troisième valeur',
    id: 'name1-3',
    name: 'name1-3',
    hint: 'Description de la troisième valeur',
  },
] as const

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

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

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

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

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

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" setup>
import { computed } from 'vue'

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

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

export type { DsfrCheckboxSetProps }

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

const emit = defineEmits<{ (e: 'update:modelValue', payload: string[]): void }>()

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

const onChange = ({ name, checked }: { name: string, checked: boolean }) => {
  const selected = checked
    ? [...props.modelValue, name]
    : props.modelValue.filter(val => val !== name)
  emit('update:modelValue', selected)
}
const ariaLabelledby = computed(() => message.value ? `${props.titleId} messages-${props.titleId}` : props.titleId)
</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"
          :name="option.name"
          :label="option.label"
          :disabled="option.disabled"
          :aria-disabled="option.disabled"
          :small="small"
          :inline="inline"
          :model-value="modelValue.includes(option.name)"
          :hint="option.hint"
          @update:model-value="onChange({ name: option.name, checked: $event })"
        />
      </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
  modelValue?: boolean
  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?: string[]
  ariaInvalid?: boolean
}