Skip to content

Champ de saisie - DsfrInput

🌟 Introduction

Le composant DsfrInput, outil essentiel dans l'arsenal de tout développeur Vue ! Que ce soit pour saisir votre nom de fromage préféré ou la date de votre dernière visite à la Tour Eiffel, DsfrInput est là pour rendre la saisie de données aussi douce qu'un croissant frais le matin 🥐 (oui, on aime bien les croissants par ici).

🛠️ Props

NomTypeDéfautObligatoireDescription
idFunction() => getRandomId(...)Identifiant unique pour l'input. Si non spécifié, un ID aléatoire est généré.
descriptionIdstringundefinedID pour la description associée à l'input. Utile pour l'accessibilité.
hintstring''Texte d'indice pour guider l'utilisateur.
labelstring''Le libellé de l'input.
labelClassstring''Classe personnalisée pour le style du libellé.
modelValuestring''La valeur liée au modèle de l'input.
wrapperClassstring''Classe personnalisée pour le style du conteneur de l'input.

Attributs implicitement déclarés

Important

Toutes les props passées à <DsfrInput> dans une template et qui ne sont pas définies dans les props seront passées à la balise <input> native du composant (cf. Attributs implicitement déclarés (Fallthrough attributes) de la documentation officielle de Vue.js.). Comme par exemple readonly.

Voici une liste non-exhaustive:

  • name
  • readonly
  • disabled
  • autocomplete
  • autofocus (déconseillé)
  • size
  • maxlength
  • pattern

Exemple :

vue
<script setup>
// (...)
</script>

<template>
  <DsfrInput
    v-model="username"
    label="Nom d’utilisateur"
    name="username"
    pattern="\w{3,20}"
  />
</template>

📡 Événements

NomDescription
update:modelValueÉvénement émis lors de la mise à jour de la valeur de l'input.

🧩 Slots

NomDescription
labelSlot pour personnaliser le contenu de la balise <label>.
required-tipSlot pour indiquer si le champ est obligatoire. Par défaut, affiche une astérisque si requis.

📝 Exemples

Exemple simple d'utilisation de DsfrInput :

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

import DsfrInput from '../DsfrInput.vue'

const name = ref('')
</script>

<template>
  <div class="fr-container fr-my-2w">
    <h2>1. Simple</h2>

    <DsfrInput
      v-model="name"
      label="Nom"
      placeholder="Jean Dupont"
      label-visible
      required
      hint="Indiquez votre nom"
    />
    <p>{{ name }}</p>

    <h2>2. Avec utilisation du slot <code>#required-tip</code></h2>

    <DsfrInput
      v-model="name"
      label="Nom"
      label-visible
      hint="Entrez votre nom complet"
      required
    >
      <template #required-tip>
        <span class="custom-required"> (requis)</span>
      </template>
    </DsfrInput>
  </div>
</template>

<style scoped>
.custom-required {
  color: red;
  font-style: italic;
}
</style>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import { ref, computed, useAttrs } from 'vue'
import type { Ref } from 'vue'

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

import type { DsfrInputProps } from './DsfrInput.types'

export type { DsfrInputProps }

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<DsfrInputProps>(), {
  id: () => getRandomId('basic', 'input'),
  descriptionId: undefined,
  hint: '',
  label: '',
  labelClass: '',
  modelValue: '',
  wrapperClass: '',
})

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

const attrs = useAttrs()

const __input: Ref<HTMLElement | null> = ref(null)
const focus = () => __input.value?.focus()

const isComponent = computed(() => props.isTextarea ? 'textarea' : 'input')
const wrapper = computed(() => props.isWithWrapper || attrs.type === 'date' || !!props.wrapperClass)
const finalLabelClass = computed(() => [
  'fr-label',
  { invisible: !props.labelVisible },
  props.labelClass,
])

defineExpose({
  focus,
})
</script>

<template>
  <label
    :class="finalLabelClass"
    :for="id"
  >
    <!-- @slot Slot pour personnaliser tout le contenu de la balise <label> -->
    <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' in $attrs && $attrs.required !== false"
          class="required"
        >*</span>
      </slot>
    </slot>

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

  <component
    :is="isComponent"
    v-if="!wrapper"
    :id="id"
    v-bind="$attrs"
    ref="__input"
    class="fr-input"
    :class="{
      'fr-input--error': isInvalid,
      'fr-input--valid': isValid,
    }"
    :value="modelValue"
    :aria-describedby="descriptionId || undefined"
    @input="$emit('update:modelValue', $event.target.value)"
  />

  <div
    v-else
    :class="[
      { 'fr-input-wrap': isWithWrapper || $attrs.type === 'date' },
      wrapperClass,
    ]"
  >
    <component
      :is="isComponent"
      :id="id"
      v-bind="$attrs"
      ref="__input"
      class="fr-input"
      :class="{
        'fr-input--error': isInvalid,
        'fr-input--valid': isValid,
      }"
      :value="modelValue"
      :aria-describedby="descriptionId || undefined"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

<style scoped>
.invisible {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
</style>
ts
export type DsfrInputProps = {
  id?: string
  descriptionId?: string
  hint?: string
  isInvalid?: boolean
  isValid?: boolean
  isTextarea?: boolean
  isWithWrapper?: boolean
  labelVisible?: boolean
  label?: string
  labelClass?: string
  modelValue?: string | number | null
  wrapperClass?: string
}

export type DsfrInputGroupProps = {
  descriptionId?: string
  hint?: string
  labelVisible?: boolean
  label?: string
  labelClass?: string
  modelValue?: string | number | null
  placeholder?: string
  errorMessage?: string | string[]
  validMessage?: string | string[]
  wrapperClass?: string
}

Avec DsfrInput, la saisie de données devient aussi élégante que la promenade dans un vignoble en automne. 🍇 Bonne programmation !