📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~4 min de lecture

:host { display: contents } en Angular : astuce ou code smell ?

TL;DR:host { display: contents } efface l'élément host du layout sans le retirer du DOM. C'est légitime pour les composants qui doivent transpercer un grid parent, mais c'est souvent le symptôme d'un wrapper inutile dans le template. Avant de l'utiliser, supprime la <div> racine et mets ses styles directement sur :host (avec un role si la sémantique compte).

Un collègue te lâche sur Discord :

Yo, j'ai une astuce pour vous !

:host {
  display: contents;
}

Ça fait disparaître le host.

Bingo. En une ligne de CSS, l'élément <app-mon-component> devient invisible pour le moteur de layout : ses enfants se comportent comme s'ils étaient directement dans le parent. Plus de wrapper qui casse un flex, plus de display: block à poser. Magique. Et c'est exactement le problème.

Cette astuce est tellement pratique qu'on l'utilise pour cacher un symptôme au lieu de traiter la cause. Décortiquons.


Ce que fait display: contents

Le navigateur arrête de générer une boîte de rendu pour l'élément, mais le garde dans le DOM et dans l'arbre d'accessibilité. Les enfants montent d'un cran dans le flux de layout.

@Component({
  selector: 'app-card',
  template: `
    <h3>{{ title() }}</h3>
    <p>{{ body() }}</p>
  `,
  styles: `
    :host {
      display: contents;
    }
  `
})
export class Card {
  readonly title = input.required<string>();
  readonly body = input.required<string>();
}

Rendu :

<div class="grid">
  <app-card>           <!-- présent dans le DOM, mais transparent pour la grid -->
    <h3>Titre</h3>
    <p>Corps</p>
  </app-card>
</div>

Pour display: grid, c'est comme si <app-card> n'existait pas : <h3> et <p> deviennent enfants directs de la grille. Pratique pour les composants destinés à s'intégrer dans le layout du parent sans le casser.


Pourquoi c'est souvent un code smell

Si tu as besoin de masquer le host, c'est probablement que ton template porte un wrapper qui ne devrait pas exister.

Le scénario typique

@Component({
  selector: 'app-stats-block',
  template: `
    <div class="stats-block">
      <span class="label">{{ label() }}</span>
      <span class="value">{{ value() }}</span>
    </div>
  `,
  styles: `
    .stats-block {
      display: flex;
      gap: 8px;
      padding: 16px;
      background: #f5f5f5;
    }
  `
})
export class StatsBlock { /* ... */ }

Tu intègres ça dans une grille parent, ça casse l'alignement. Réflexe : :host { display: contents }. Sauf que la .stats-block est déjà un wrapper inutile : le host pourrait porter la classe directement.

La vraie correction

@Component({
  selector: 'app-stats-block',
  host: {
    class: 'stats-block',
  },
  template: `
    <span class="label">{{ label() }}</span>
    <span class="value">{{ value() }}</span>
  `,
  styles: `
    :host {
      display: flex;
      gap: 8px;
      padding: 16px;
      background: #f5f5f5;
    }
  `
})
export class StatsBlock { /* ... */ }

Un niveau de DOM en moins, les styles vivent là où ils doivent vivre (sur le host), et le composant s'intègre proprement dans la grille parent — sans aucune astuce CSS.

Règle de pouce : avant de poser display: contents, regarde ton template. Si tu as une <div> racine qui porte toutes les classes et tous les styles, c'est elle le bug — pas le host.


L'objection sémantique : « le host n'est pas un vrai élément HTML »

L'argument qui revient à chaque fois :

Oui mais <app-stats-block> ce n'est pas sémantique, le SEO va pleurer.

C'est vrai par défaut. Et c'est trivial à corriger.

Donne-lui un rôle

@Component({
  selector: 'app-article-summary',
  host: {
    role: 'article',
    class: 'article-summary',
  },
  template: `
    <h2>{{ title() }}</h2>
    <p>{{ excerpt() }}</p>
  `,
})
export class ArticleSummary { /* ... */ }

Le custom element devient une article aux yeux des lecteurs d'écran et des crawlers. Tu peux faire pareil avec role="region", role="navigation", role="status", etc.

Ou utilise un selector d'attribut

Pour rester strict côté HTML natif, projette le composant sur un élément sémantique :

@Component({
  selector: 'section[appHero]',
  template: `<ng-content />`,
})
export class Hero {}

Utilisation :

<section appHero>
  <h1>Titre</h1>
</section>

Le composant est la <section>. Zéro wrapper, sémantique native, SEO heureux.


Les cas où display: contents est légitime

Ce n'est pas un anti-pattern absolu. Il y a des contextes où tu n'as pas le choix.

  1. Composants de projection multi-slot qui doivent s'effacer dans une grille parent dont tu ne contrôles pas le layout (ex. un wrapper de design system qui orchestre du <ng-content> dans plusieurs zones).
  2. Wrappers de routing quand <router-outlet> ou un guard impose un composant intermédiaire qui ne doit rien rendre visuellement.
  3. Composants tiers que tu intègres dans une grille existante et dont le sélecteur n'est pas un attribut.

Dans ces cas-là, assume-le : commente la raison, et garde un role si la sémantique compte.

@Component({
  selector: 'app-grid-cell',
  host: {
    role: 'gridcell',
  },
  template: `<ng-content />`,
  styles: `
    /* Le host doit s'effacer pour que les enfants soient cellules directes du parent grid. */
    :host {
      display: contents;
    }
  `
})
export class GridCell {}

La règle finale

display: contents doit être un choix assumé, pas une rustine pour cacher un wrapper qu'on n'a pas su supprimer.

Avant de l'écrire, pose-toi trois questions :

  1. Mon template a-t-il une <div> racine ? Si oui, déplace ses styles sur le host et supprime-la.
  2. Mon host a-t-il besoin d'être sémantique ? Si oui, ajoute un role (ou utilise un selector d'attribut sur un tag natif).
  3. Le parent a-t-il vraiment besoin que mon composant disparaisse du layout ? Si la réponse est oui après les deux premières questions, alors display: contents est la bonne réponse.

Sinon, tu transformes ton composant Angular en fantôme — présent dans le DOM, invisible pour le layout, et probablement aussi invisible pour ta future toi qui se demandera pourquoi cette grille déborde de trois niveaux d'éléments transparents.


Envie de découvrir EasyAngularKit et son programme ?

Découvre EasyAngularKit : https://www.easyangularkit.com/easyangularkit

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.