DsfrButton
🌟 Introduction
Le bouton est un élément d’interaction avec l’interface permettant à l’utilisateur d’effectuer une action.
Le DsfrButton
est un composant Vue élégant et réutilisable, conçu pour simplifier la création de boutons personnalisés. Il intègre des tailles ajustables, des icônes optionnelles et un gestionnaire de clics, tout en respectant le style de DSFR
. Son utilisation est simple, avec une grande flexibilité pour s'adapter à différents contextes.
🏅 La documentation sur l’alerte sur le DSFR
La story sur l’alerte sur le storybook de VueDsfr📐 Structure
Les boutons sont composés de :
- Un label - obligatoire, soit en utilisant la prop
label
soit en utilisant le slot par défaut ; - Une icône, pouvant être modifiée (voir les icônes disponibles) - optionnelle.
🛠️ Props
Nom | Type | Défaut | Obligatoire | Description |
---|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Taille du bouton. Peut être 'sm', 'md', ou 'lg'. | |
icon | string | object | undefined | Icône à afficher dans le bouton. Peut être un nom ou une configuration d'icône. | |
label | string | undefined | Étiquette textuelle du bouton. Si le label est laissé à undefined, le slot par défaut doit contenir du texte ! | |
onClick | Function | () => {} | Fonction appelée lors du clic sur le bouton. |
📡 Évenements
click
: Émis lorsque le bouton est cliqué.
🧩 Slots
default
: Emplacement pour le contenu personnalisé du bouton. Inséré dans<button class="fr-btn"><span">
.
✨ Les groupes de boutons
📝 Exemples
Un bouton large avec une icône 'maison' à gauche et le texte 'Accueil' :
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrButton from '../DsfrButton.vue'
const nb = ref(0)
const handleClick = () => {
nb.value++
}
</script>
<template>
<div class="fr-container fr-my-2w">
<DsfrButton
size="lg"
icon="fr-icon-home-4-fill"
label="Accueil"
@click="handleClick()"
/>
Cliqué {{ nb }} fois
</div>
</template>
Un petit bouton avec le texte 'Aller plus loin', du contenu supplémentaire dans le slot par défaut, et une icône à droite :
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrButton from '../DsfrButton.vue'
const nb = ref(0)
const handleClick = () => {
nb.value++
}
</script>
<template>
<div class="fr-container fr-my-2w">
<DsfrButton
size="sm"
label="Aller plus loin"
icon="fr-icon-arrow-right-line"
icon-right
@click="handleClick()"
>
(Contenu <em>supplémentaire</em>)
</DsfrButton>
Cliqué {{ nb }} fois
</div>
</template>
📝 (Presque) toutes les variantes 🌈 de boutons
<script lang="ts" setup>
import DsfrButton from '../DsfrButton.vue'
</script>
<template>
<div class="fr-container fr-my-2w">
<div class="flex gap-4 flex-end w-full">
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Bouton primaire"
primary
/>
<DsfrButton
label="Bouton secondaire"
secondary
/>
<DsfrButton
label="Bouton tertiaire"
tertiary
/>
<DsfrButton
label="Bouton tertiaire sans bordure"
tertiary
no-outline
/>
</div>
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Petit bouton primaire"
size="sm"
primary
/>
<DsfrButton
label="Petit bouton secondaire"
size="sm"
secondary
/>
<DsfrButton
label="Petit bouton tertiaire"
size="sm"
tertiary
/>
<DsfrButton
label="Petit bouton tertiaire sans bordure"
size="sm"
tertiary
no-outline
/>
</div>
</div>
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Gros bouton primaire"
size="lg"
primary
/>
<DsfrButton
label="Gros bouton secondaire"
size="lg"
secondary
/>
<DsfrButton
label="Gros bouton tertiaire"
size="lg"
tertiary
/>
<DsfrButton
label="Gros bouton tertiaire sans bordure"
size="lg"
tertiary
no-outline
/>
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Bouton primaire avec icône DSFR"
icon="fr-icon-moon-line"
primary
/>
<DsfrButton
label="Bouton secondaire avec icône DSFR"
icon="fr-icon-moon-line"
secondary
/>
<DsfrButton
label="Bouton tertiaire avec icône DSFR"
icon="fr-icon-moon-line"
tertiary
/>
<DsfrButton
label="Bouton tertiaire sans bordure avec icône DSFR"
icon="fr-icon-moon-line"
tertiary
no-outline
/>
</div>
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Bouton primaire avec icône iconify"
icon="ri-moon-line"
primary
/>
<DsfrButton
label="Bouton secondaire avec icône iconify animée (spin)"
:icon="{ name: 'ri-refresh-line', animation: 'spin' }"
secondary
/>
<DsfrButton
label="Bouton tertiaire avec icône iconify animée (spin-pulse)"
:icon="{ name: 'ri-moon-line', animation: 'spin-pulse' }"
tertiary
/>
<DsfrButton
label="Bouton 3re ss bordure avec icône OVI colorée avec couleur DSFR"
title="Bouton tertiaire sans bordure avec icône iconify colorée avec une couleur du DSFR"
:icon="{ name: 'ri-moon-line', fill: 'var(--success-425-625)' }"
tertiary
no-outline
/>
</div>
<div class="flex gap-4 flex-end w-full">
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Bouton primaire icône seulement avec icône DSFR"
icon="fr-icon-moon-line"
icon-only
primary
/>
<DsfrButton
label="Bouton secondaire avec icône DSFR"
icon="fr-icon-moon-line"
icon-only
secondary
/>
<DsfrButton
label="Bouton tertiaire avec icône DSFR animée"
icon="fr-icon-moon-line"
icon-only
tertiary
/>
<DsfrButton
label="Bouton 3re ss bordure avec icône OVI colorée avec couleur DSFR"
title="Bouton tertiaire sans bordure avec icône DSFR colorée avec une couleur du DSFR"
icon="fr-icon-moon-line"
icon-only
tertiary
no-outline
/>
</div>
<div class="flex flex-col gap-4 flex-end w-full">
<DsfrButton
label="Bouton primaire icône seulement avec icône iconify"
icon="ri-moon-line"
icon-only
primary
/>
<DsfrButton
label="Bouton secondaire avec icône iconify animée (spin)"
:icon="{ name: 'ri-refresh-line', animation: 'spin' }"
icon-only
secondary
/>
<DsfrButton
label="Bouton tertiaire avec icône iconify animée (spin-pulse)"
:icon="{ name: 'ri-moon-line', animation: 'spin-pulse' }"
icon-only
tertiary
/>
<DsfrButton
label="Bouton 3re ss bordure avec icône OVI colorée avec couleur DSFR"
title="Bouton tertiaire sans bordure avec icône iconify colorée avec une couleur du DSFR"
:icon="{ name: 'ri-moon-line', fill: 'var(--success-425-625)' }"
icon-only
tertiary
no-outline
/>
</div>
</div>
</div>
</div>
</template>
⚙️ Code source du composant
<script lang="ts" setup>
import { computed, ref } from 'vue'
import VIcon from '../VIcon/VIcon.vue'
import type { DsfrButtonProps } from './DsfrButton.types'
export type { DsfrButtonProps }
const props = withDefaults(defineProps<DsfrButtonProps>(), {
size: 'md',
icon: undefined,
label: undefined,
onClick: () => undefined,
})
const sm = computed(() => ['sm', 'small'].includes(props.size))
const md = computed(() => ['md', 'medium'].includes(props.size))
const lg = computed(() => ['lg', 'large'].includes(props.size))
const btn = ref<{ focus: () => void } | null>(null)
const focus = () => {
btn.value?.focus()
}
defineExpose({ focus })
const dsfrIcon = computed(() => typeof props.icon === 'string' && props.icon.startsWith('fr-icon-'))
const defaultScale = computed(() => props.iconOnly ? 1.25 : 0.8325)
const iconProps = computed(() => typeof props.icon === 'string'
? { scale: defaultScale.value, name: props.icon }
: { scale: defaultScale.value, ...props.icon },
)
</script>
<template>
<button
ref="btn"
class="fr-btn"
:class="{
'fr-btn--secondary': secondary && !tertiary,
'fr-btn--tertiary': tertiary && !secondary && !noOutline,
'fr-btn--tertiary-no-outline': tertiary && !secondary && noOutline,
'fr-btn--sm': sm,
'fr-btn--md': md,
'fr-btn--lg': lg,
'fr-btn--icon-right': !iconOnly && dsfrIcon && iconRight,
'fr-btn--icon-left': !iconOnly && dsfrIcon && !iconRight,
'inline-flex': !dsfrIcon,
reverse: iconRight && !dsfrIcon,
'justify-center': !dsfrIcon && iconOnly,
[icon as string]: dsfrIcon,
}"
:title="iconOnly ? label : undefined"
:disabled="disabled"
:aria-disabled="disabled"
:style="(!dsfrIcon && iconOnly) ? { 'padding-inline': '0.5rem' } : {}"
@click="onClick ? onClick($event) : () => {}"
>
<VIcon
v-if="icon && !dsfrIcon"
v-bind="iconProps"
/>
<span v-if="!iconOnly">
{{ label }}
<!-- @slot Slot par défaut pour le contenu du bouton. Sera dans `<button class="fr-btn"><span">` -->
<slot />
</span>
</button>
</template>
<style scoped>
.inline-flex {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.reverse {
flex-direction: row-reverse;
}
</style>
import type { ButtonHTMLAttributes } from 'vue'
import type VIcon from '../VIcon/VIcon.vue'
export type DsfrButtonProps = {
disabled?: boolean
label?: string
secondary?: boolean
tertiary?: boolean
iconRight?: boolean
iconOnly?: boolean
noOutline?: boolean
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined
icon?: string | InstanceType<typeof VIcon>['$props']
onClick?: ($event: MouseEvent) => void
}
export type DsfrButtonGroupProps = {
buttons?: (DsfrButtonProps & ButtonHTMLAttributes)[]
reverse?: boolean
equisized?: boolean
iconRight?: boolean
align?: 'right' | 'center' | '' | undefined
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | true | undefined
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | undefined
}
Et voilà ! Notre DsfrButton est prêt à illuminer votre interface avec style et fonctionnalité. N'oubliez pas d'appuyer sur ces boutons avec panache ! 🚀