Skip to content

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

NomTypeDescriptionValeur par défautObligatoire
titlestringLe titre de la tuile.'Titre de la tuile'
imgSrcstringSource de l'image à afficher.undefined
svgPathstringChemin vers le SVG à afficher.undefined
svgAttrsRecord<string, unknown>Attributs pour le SVG.{ viewBox: '0 0 80 80', width: '80px', height: '80px' }
descriptionstringDescription de la tuile.undefined
detailsstringDétails supplémentaires à afficher dans la tuile.undefined
disabledbooleanSi vrai, la tuile est désactivée et non cliquable.false
horizontalbooleanSi vrai, la tuile est affichée horizontalement.false
vertical'md' | 'lg'Taille verticale de la tuile.undefined
toRouteLocationRawLien ou destination du routeur Vue.'#'
titleTagTitleTagTag HTML pour le titre.'h3'
downloadbooleanSi vrai, le lien est un téléchargement.false
smallbooleanSi vrai, affiche une tuile plus petite.false
iconbooleanSi faux, n'affiche pas d'icône dans la tuile.true
noBorderbooleanSi vrai, n'affiche pas de bordure autour de la tuile.false
shadowbooleanSi vrai, affiche une ombre autour de la tuile.false
noBackgroundbooleanSi vrai, n'affiche pas de fond dans la tuile.false
greybooleanSi 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
}