Skip to content

Liste déroulante - DsfrSelect

🌟 Introduction

Le DsfrSelect est un composant Vue permettant à un utilisateur de choisir un élément dans une liste donnée.

La liste déroulante fournit une liste d’option parmi lesquelles l’utilisateur peut choisir. Seule la partie visible du composant est stylisé : la liste d’options déroulées conserve le style du navigateur.

🏅 La documentation sur liste déroulante sur le DSFR

La story sur le select sur le storybook de VueDsfr

🛠️ Props

NomTypeDéfautObligatoireDescription
modelValuestring | numberValeur associée à l'option sélectionnée.
requiredbooleanIndique si le select est obligatoire.
disabledbooleanIndique si le select est désactivé.
options(string | undefined | { value: string | undefined, text: string disabled?: boolean})[][]Options à sélectionner
optionGroups({ label: string, options: (DsfrSelectOption | { value: DsfrSelectOption, text: string, disabled?: boolean })[], disabled?: boolean })[][]Groupes d'options
labelstring''Texte du label associé au select.
hideLabelbooleanfalseMasque le Label (reste visible pour les lecteurs d'écran)
namestringNom du champ.
description obsolètestringObsolète, utiliser hint plutôt.
hintstringTexte d'indice pour guider.
successMessagestring''Message de validation à afficher en dessous du select.
errorMessagestring''Message d'erreur à afficher en dessous du select.
defaultUnselectedTextstring'Sélectionner une option'Si true, l'infobulle s'affiche au survol.
selectIdstringuseRandomId('select')Identifiant unique pour le select. Utilisé pour l'accessibilité.

📡Évenements

DsfrSelect émet l'événement suivant :

NomtypeDescription
update:modelValuestring | numberEst émis lorsque la valeur du select change.

🧩 Slots

  • Aucun slot n'est disponible dans ce ce composant.

📝 Exemples

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

import DsfrSelect from '../DsfrSelect.vue'

const selectedOption1 = ref(0)
const selectedOption2 = ref(null)
const selectedOption3 = ref('')
const selectedOption4 = ref('')
</script>

<template>
  <div
    class="flex flex-col"
  >
    <DsfrSelect
      v-model.number="selectedOption1"
      label="Label du select"
      :options="[1, 2, 3, 4, 5]"
    />
    Select 1 : {{ selectedOption1 }} ({{ typeof selectedOption1 }})
    <DsfrSelect
      v-model="selectedOption2"
      label="Label du select 1"
      description="Description du select"
      :options="['Value 1', 'Value 2', 'Value 3', 'Value 4', 'Value 5']"
    />
    Select 2 : {{ selectedOption2 }} ({{ typeof selectedOption2 }})
    <DsfrSelect
      v-model="selectedOption3"
      label="Label du select 3"
      border-bottom
      required
      disabled
      :options="[
        { value: 'Value 1', text: 'Text 1' },
        { value: 'Value 2', text: 'Text 2' },
        { value: 'Value 3', text: 'Text 3' },
        { value: 'Value 4', text: 'Text 4' },
        { value: 'Value 5', text: 'Text 5' },
      ]"
    />
    Select 3 : {{ selectedOption3 }} ({{ typeof selectedOption3 }})
    <DsfrSelect
      v-model="selectedOption4"
      label="Select 4 avec groupes d'options (optgroup) dont 1 est désactivé"
      border-bottom
      required
      :option-groups="[
        {
          label: 'groupe 1',
          options: [
            { value: 'Value 1', text: 'Text 1' },
            { value: 'Value 2', text: 'Text 2' },
          ],
        },
        {
          label: 'groupe 2',
          disabled: true,
          options: [
            { value: 'Value 3', text: 'Text 3' },
            { value: 'Value 4', text: 'Text 4' },
            { value: 'Value 5', text: 'Text 5' },
          ],
        },
      ]"
    />
    Select 4 : {{ selectedOption4 }} ({{ typeof selectedOption4 }})
  </div>
</template>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import type { DsfrSelectProps } from './DsfrSelect.types'

import { computed } from 'vue'

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

export type { DsfrSelectProps }

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<DsfrSelectProps>(), {
  selectId: () => useRandomId('select'),
  modelValue: undefined,
  options: () => [],
  optionGroups: () => [],
  label: '',
  name: undefined,
  description: undefined,
  hint: undefined,
  successMessage: '',
  errorMessage: '',
  defaultUnselectedText: 'Sélectionner une option',
})

defineEmits<{
  /** Événement émis lors du changement de l'option sélectionnée */
  'update:modelValue': [payload: string | number]
}>()

defineSlots<{
  /**
   * Slot pour personnaliser tout le contenu de la balise <label>
   * cf. [DsfrInput](/?path=/story/composants-champ-de-saisie-champ-simple-dsfrinput--champ-avec-label-personnalise).
   * Une **props porte le même nom pour un label simple** (texte sans mise en forme)
   */
  label: () => any
  /**
   * Slot pour indiquer que le champ est obligatoire.
   * Par défaut, met une astérisque si `required` est à true (dans un `<span class="required">`)
   */
  'required-tip': () => any
}>()

if (props.description) {
  console.warn(
    '[DsfrSelect] : La prop `description` est obsolète. Veuillez utiliser `hint` à la place.',
  )
}

const message = computed(() => {
  return props.errorMessage || props.successMessage
})
const messageType = computed(() => {
  return props.errorMessage ? 'error' : 'valid'
})
</script>

<template>
  <div
    class="fr-select-group"
    :class="{ [`fr-select-group--${messageType}`]: message }"
  >
    <label
      class="fr-label"
      :class="{ 'fr-sr-only': hideLabel }"
      :for="selectId"
    >
      <slot name="label">
        {{ label }}
        <slot name="required-tip">
          <span
            v-if="required"
            class="required"
          >&nbsp;*</span>
        </slot>
      </slot>

      <span
        v-if="hint ?? description"
        class="fr-hint-text"
      >{{ hint ?? description }}</span>
    </label>

    <select
      :id="selectId"
      :class="{ [`fr-select--${messageType}`]: message }"
      class="fr-select"
      :name="name || selectId"
      :disabled="disabled"
      :aria-disabled="disabled"
      :required="required"
      v-bind="$attrs"
      @change="$emit('update:modelValue', ($event.target as HTMLInputElement)?.value)"
    >
      <option
        :selected="!options.some(option => (typeof option !== 'object' || option === null) ? option === modelValue : option.value === modelValue)"
        disabled
        value=""
      >
        {{ defaultUnselectedText }}
      </option>

      <option
        v-for="(option, index) in options"
        :key="index"
        :selected="modelValue === option || (typeof option === 'object' && option!.value === modelValue)"
        :value="typeof option === 'object' ? option!.value : option"
        :disabled="!!(disabled || typeof option === 'object' && option!.disabled)"
        :aria-disabled="!!(disabled || typeof option === 'object' && option!.disabled)"
      >
        {{ typeof option === 'object' ? option!.text : option }}
      </option>
      <optgroup
        v-for="(optionGroup, index) in optionGroups"
        :key="index"
        :label="optionGroup.label"
        :disabled="optionGroup.disabled"
        :aria-disabled="!!optionGroup.disabled"
      >
        <option
          v-for="(option, idx) in optionGroup.options"
          :key="idx"
          :selected="modelValue === option || (typeof option === 'object' && option!.value === modelValue)"
          :value="typeof option === 'object' ? option!.value : option"
          :disabled="!!(disabled || typeof option === 'object' && option!.disabled || optionGroup.disabled)"
          :aria-disabled="!!(disabled || typeof option === 'object' && option!.disabled || optionGroup.disabled)"
        >
          {{ typeof option === 'object' ? option!.text : option }}
        </option>
      </optgroup>
    </select>

    <p
      v-if="message"
      :id="`select-${messageType}-desc-${messageType}`"
      :class="`fr-${messageType}-text`"
    >
      {{ message }}
    </p>
  </div>
</template>
ts
export type DsfrSelectOption = string | number | null | undefined
export type DsfrSelectProps = {
  required?: boolean
  disabled?: boolean
  selectId?: string
  name?: string
  description?: string
  hint?: string
  modelValue?: DsfrSelectOption
  label?: string
  hideLabel?: boolean
  options?: (DsfrSelectOption | { value: DsfrSelectOption, text: string, disabled?: boolean })[]
  successMessage?: string
  errorMessage?: string
  defaultUnselectedText?: string
  optionGroups?: ({ label: string, options: (DsfrSelectOption | { value: DsfrSelectOption, text: string, disabled?: boolean })[], disabled?: boolean })[]
}