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
labelstring''Texte du label associé au select.
namestringNom du champ.
descriptionstringSi true, l'infobulle s'affiche au survol.
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.
selectIdstringgetRandomId('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('')
</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
      :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 }})
  </div>
</template>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import { computed } from 'vue'
import { getRandomId } from '../../utils/random-utils'

import type { DsfrSelectProps } from './DsfrSelect.types'

export type { DsfrSelectProps }

defineOptions({
  inheritAttrs: false,
})

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

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

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"
      :for="selectId"
    >
      <!-- @slot 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) -->
      <slot name="label">
        {{ label }}
        <!-- @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>

      <span
        v-if="description"
        class="fr-hint-text"
      >{{ 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="modelValue == null"
        disabled
        hidden
      >
        {{ 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="!!(typeof option === 'object' && option!.disabled)"
        :aria-disabled="!!(typeof option === 'object' && option!.disabled)"
      >
        {{ typeof option === 'object' ? option!.text : option }}
      </option>
    </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
  modelValue?: DsfrSelectOption
  label?: string
  options?: (DsfrSelectOption | { value: DsfrSelectOption, text: string, disabled?: boolean })[]
  successMessage?: string
  errorMessage?: string
  defaultUnselectedText?: string
}