Tuile - DsfrTile
🌟 Introduction
La tuile est un raccourci ou point d’entrée qui redirige les utilisateurs vers des pages de contenu. Elle fait généralement partie d'une collection ou liste de tuiles similaires. La tuile n’est jamais présentée de manière isolée.
Le composant DsfrTile
est une tuile flexible et stylisée, idéale pour afficher des informations sous forme de cartes visuelles dans une interface utilisateur. Ce composant permet d'intégrer des images, des SVG, des descriptions, des détails et des liens, tout en offrant de nombreuses options de personnalisation visuelle.
🏅 La documentation sur la tuile sur le DSFR
La story sur la tuile sur le storybook de VueDsfr📐 Structure
- Un pictogramme
fr-artwork
uniquement (jpg, png, svg, etc), optionnel (des SVG sont à trouver dans@gouvfr/dsfr/dist/artwork/**
) - Une première zone de détail, composée d’une précision, sous forme de tags (cliquables ou non) ou de badges (jusqu'à 4 éléments), optionnels
- Un titre (prop
title
) reprenant celui de l’objet visé (page de destination, action, site), obligatoire - Une description (prop
description
), optionnelle - Une deuxième zone de détail (prop
details
), composée d’un texte, optionnelle - Une icône illustrative (par défaut, une flèche), optionnelle
🛠️ Props
Nom | Type | Description | Valeur par défaut | Obligatoire |
---|---|---|---|---|
title | string | Le titre de la tuile. | 'Titre de la tuile' | ✅ |
imgSrc | string | Source de l'image à afficher. | undefined | |
svgPath | string | Chemin vers le SVG à afficher. | undefined | |
svgAttrs | Record<string, unknown> | Attributs pour le SVG. | { viewBox: '0 0 80 80', width: '80px', height: '80px' } | |
description | string | Description de la tuile. | undefined | |
details | string | Détails supplémentaires à afficher dans la tuile. | undefined | |
disabled | boolean | Si vrai, la tuile est désactivée et non cliquable. | false | |
horizontal | boolean | Si vrai, la tuile est affichée horizontalement. | false | |
vertical | 'md' | 'lg' | Taille verticale de la tuile. | undefined | |
to | RouteLocationRaw | Lien ou destination du routeur Vue. | '#' | |
titleTag | TitleTag | Tag HTML pour le titre. | 'h3' | |
download | boolean | Si vrai, le lien est un téléchargement. | false | |
small | boolean | Si vrai, affiche une tuile plus petite. | false | |
icon | boolean | Si faux, n'affiche pas d'icône dans la tuile. | true | |
noBorder | boolean | Si vrai, n'affiche pas de bordure autour de la tuile. | false | |
shadow | boolean | Si vrai, affiche une ombre autour de la tuile. | false | |
noBackground | boolean | Si vrai, n'affiche pas de fond dans la tuile. | false | |
grey | boolean | Si vrai, affiche un fond gris pour la tuile. | false |
📡 Événements
Ce composant ne déclenche pas d'événements spécifiques.
🧩 Slots
header
: Slot pour insérer du contenu personnalisé dans l'en-tête de la tuile.
📝 Exemples
vue
<script lang="ts" setup>
import svgCityHall from '@gouvfr/dsfr/dist/artwork/pictograms/buildings/city-hall.svg'
import svgHouse from '@gouvfr/dsfr/dist/artwork/pictograms/buildings/house.svg'
import svgSchool from '@gouvfr/dsfr/dist/artwork/pictograms/buildings/school.svg'
import svgContract from '@gouvfr/dsfr/dist/artwork/pictograms/document/contract.svg'
import svgDocument from '@gouvfr/dsfr/dist/artwork/pictograms/document/driving-licence.svg'
import { getCurrentInstance, ref } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import DsfrButton from '../../DsfrButton/DsfrButton.vue'
import DsfrTile from '../DsfrTile.vue'
const title = 'Ma formidable tuile'
const imgSrc = ref<string>()
const svgPath = ref<string | undefined>(svgSchool)
const description = 'Une tuile absolument formidable'
const disabled = ref(false)
const horizontal = ref(false)
const details = 'Détails (optionnel)'
const to = '/dummy-path'
const titleTag = 'h2'
const download = false
const small = false
const icon = ref(true)
const noBorder = false
const shadow = false
const noBackground = false
const grey = false
function toggleSvgImg () {
svgPath.value = svgPath.value === undefined ? getRandomSvg() : undefined
imgSrc.value = imgSrc.value === undefined ? `https://loremflickr.com/80/80/cat?random=${Math.round(Math.random() * 10)}` : undefined
}
function setRandomSvg () {
imgSrc.value = undefined
svgPath.value = getRandomSvg()
}
function getRandomSvg () {
const svgs = [svgSchool, svgCityHall, svgHouse, svgContract, svgDocument]
return svgs[Math.floor(Math.random() * svgs.length)]
}
const app = getCurrentInstance()
app?.appContext.app.use(
createRouter({
history: createWebHistory(),
routes: [
{ path: '', component: { template: '<div>Accueil</div>' } },
{ path: '/dummy-path', component: { template: '<div>DummyPath</div>' } },
],
}),
)
</script>
<template>
<div class="fr-container fr-my-2v">
<DsfrTile
:title="title"
:img-src="imgSrc"
:svg-path="svgPath"
:description="description"
:details="details"
:horizontal="horizontal"
:disabled="disabled"
:to="to"
:title-tag="titleTag"
:download="download"
:small="small"
:icon="icon"
:no-border="noBorder"
:shadow="shadow"
:no-background="noBackground"
:grey="grey"
/>
<div class="fr-my-2v flex gap-2">
<DsfrButton
type="button"
:label="disabled ? 'Activer' : 'Désactiver'"
secondary
@click="disabled = !disabled"
/>
<DsfrButton
type="button"
label="Horizontal / Vertical"
secondary
@click="horizontal = !horizontal"
/>
<DsfrButton
type="button"
label="Avec / sans icône"
secondary
@click="icon = !icon"
/>
<DsfrButton
type="button"
label="Image / SVG"
secondary
@click="toggleSvgImg()"
/>
<DsfrButton
type="button"
label="SVG aléatoire"
secondary
@click="setRandomSvg()"
/>
</div>
</div>
</template>
⚙️ Code source du composant
vue
<script lang="ts" setup>
import { computed } from 'vue'
import type { DsfrTileProps } from './DsfrTiles.types'
export type { DsfrTileProps }
const props = withDefaults(defineProps<DsfrTileProps>(), {
title: 'Titre de la tuile',
imgSrc: undefined,
svgPath: undefined,
svgAttrs: () => ({ viewBox: '0 0 80 80', width: '80px', height: '80px' }),
description: undefined,
details: undefined,
horizontal: false,
vertical: undefined,
to: '#',
titleTag: 'h3',
icon: true,
})
const defaultSvgAttrs = { viewBox: '0 0 80 80', width: '80px', height: '80px' }
const isExternalLink = computed(() => {
return typeof props.to === 'string' && props.to.startsWith('http')
})
</script>
<template>
<div
class="fr-tile fr-enlarge-link"
:class="[{
'fr-tile--disabled': disabled,
'fr-tile--sm': small === true,
'fr-tile--horizontal': horizontal === true,
'fr-tile--vertical': horizontal === false || vertical === 'md' || vertical === 'lg',
'fr-tile--vertical@md': vertical === 'md',
'fr-tile--vertical@lg': vertical === 'lg',
'fr-tile--download': download,
'fr-tile--no-icon': icon === false,
'fr-tile--no-border': noBorder,
'fr-tile--no-background': noBackground,
'fr-tile--shadow': shadow,
'fr-tile--grey': grey,
'fr-enlarge-button': enlarge,
}]"
>
<div class="fr-tile__body">
<div class="fr-tile__content">
<component
:is="titleTag"
class="fr-tile__title"
>
<a
v-if="isExternalLink"
class="fr-tile__link"
target="_blank"
rel="noopener noreferrer"
:download="download"
:href="disabled ? '' : (to as string)"
>{{ title }}</a>
<RouterLink
v-if="!isExternalLink"
:download="download"
class="fr-tile__link"
:to="disabled ? '' : to"
>
{{ title }}
</RouterLink>
</component>
<p
v-if="description"
class="fr-tile__desc"
>
{{ description }}
</p>
<p
v-if="details"
class="fr-tile__detail"
>
{{ details }}
</p>
<div
v-if="$slots['start-details']"
class="fr-tile__start"
>
<!-- @slot Slot pour les détails d’une tuile sous forme de tags (cliquables ou non) ou de badges (4 maximum) -->
<slot name="start-details" />
</div>
</div>
</div>
<div class="fr-tile__header">
<slot name="header" />
<div
v-if="imgSrc || svgPath"
class="fr-tile__pictogram"
>
<img
v-if="imgSrc"
:src="imgSrc"
class="fr-artwork"
alt=""
>
<svg
v-else
aria-hidden="true"
class="fr-artwork"
v-bind="{ ...defaultSvgAttrs, ...svgAttrs }"
>
<use
class="fr-artwork-decorative"
:href="`${svgPath}#artwork-decorative`"
/>
<use
class="fr-artwork-minor"
:href="`${svgPath}#artwork-minor`"
/>
<use
class="fr-artwork-major"
:href="`${svgPath}#artwork-major`"
/>
</svg>
<!-- L'alternative de l'image (attribut alt) doit à priori rester vide car l'image est illustrative et ne doit pas être restituée aux technologies d’assistance. Vous pouvez toutefois remplir l'alternative si vous estimer qu'elle apporte une information essentielle à la compréhension du contenu non présente dans le texte -->
</div>
</div>
</div>
</template>
<style scoped>
.fr-tile.fr-tile--disabled {
background-color: var(--background-disabled-grey);
box-shadow: inset 0 0 0 1px var(--border-default-grey), inset 0 -0.25rem 0 0 var(--border-disabled-grey);
}
.fr-tile.fr-tile--disabled a {
cursor: not-allowed;
}
</style>
ts
import type { RouteLocationRaw } from 'vue-router'
export type DsfrTileProps = {
title?: string
imgSrc?: string
svgPath?: string
svgAttrs?: Record<string, unknown>
description?: string
details?: string
disabled?: boolean
horizontal?: boolean
vertical?: 'md' | 'lg'
to?: RouteLocationRaw
titleTag?: string
download?: boolean
small?: boolean
icon?: boolean
noBorder?: boolean
shadow?: boolean
noBackground?: boolean
grey?: boolean
enlarge?: boolean
}
export type DsfrTilesProps = {
tiles?: (DsfrTileProps & { containerClass?: string })[]
horizontal?: boolean
}