Vue normale

Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.
À partir d’avant-hierDesign

Deux années avec Tailwind CSS, quel bilan?

Tailwind ou pas Tailwind ? Ce framework CSS, fondé sur la méthodologie atomique, divise les rangs des intégratrices et intégrateurs. Chaque camp y trouve des arguments souvent très tranchés : on aime pas du tout ou bien on adore, il reste peu de place pour le "ça dépend".

Au sein de notre agence web Alsacréations, spécialisée dans les domaines front-end et dans l'accessibilité, nous avons expérimenté Tailwind depuis un peu plus de deux années à présent. Cet article détaille les leçons que nous avons tirées de l'usage ce framework.

Quelques précisions concernant notre contexte d'agence : nous ne sommes pas une start-up, nous intégrons des maquettes fournies par le client ou designées en interne et validées par le client. Même s'il nous arrive d'appliquer un framework pour un client (Bootstrap, Tailwind), notre expérience de plusieurs (dizaines d')années en intégration nous interdit le design de nouvelles pages "à l'arrache" comme certains pourraient être tentés de le faire avec ce genre d'outils.

Le mois de juin 2020 marque notre première intégration pour un client réalisée à l'aide de Tailwind CSS, puis une demi-douzaine de projets ont suivi. Sans oublier quelques projets personnels ou secrets.

En novembre 2020 nous publions sur Alsacréations l'article "Tailwind CSS, découverte du framework original et innovant". Quelques mois plus tard paraissent nos Guidelines Tailwind CSS publiques.

Enfin, en mai 2021, nous publions une sorte de synthèse sur la question : "Quels framework et méthodologie CSS choisir ?".

Page d'accueil de TailwindCSS

Point de départ : quelles étaient les problématiques à résoudre ?

Aucun projet d'intégration ne se déroule à la perfection, surtout dans la durée, et même au sein d'une agence web qui se considère comme compétente en la matière.

Rien que du côté CSS, les écueils que peut traverser un projet sont nombreux :

  1. Séparation entre le fond (HTML) et la forme (CSS) : chacun devrait pouvoir s'occuper de son job et rester indépendant de l'autre.
  2. Duplications des sélecteurs CSS (difficile de trouver, renommer, déplacer, supprimer les sélecteurs)
  3. Collisions de noms (difficile de trouver des noms cohérents au fur et à mesure que le projet grossit + risque de choisir un nom déjà existant et d'écraser une partie des styles)
  4. Spécificité des sélecteurs (ajouter du poids pour écraser un sélecteur a un effet "boule de neige" pour la prochaine modification à opérer)
  5. Cascade (les règles de cascade CSS ne favorisent pas la compréhension ou la maintenabilité)
  6. Code mort (le code inutilisé s'accumule au fur et à mesure que le projet grossit)
La cascade CSS illustrée (source Medium)

Notre expérience nous a souvent permis d'atténuer ces problèmes. Et quand je parle d'expérience, j'y inclus les différentes méthodologies que nous avons pu adopter chez Alsacréations : OOCSS dans un premier temps, BEM (un peu) et enfin Tailwind CSS.

Que nous apporte Tailwind CSS ?

Nous avons pu constater au cours de ces deux années d'usage que les promesses faites par Tailwind sont parfaitement tenues.

Les points suivants comptent parmi les plus retenus et cités dans notre agence :

  • Résout plusieurs problématiques principales de CSS (nommage, spécificité, code mort, duplication des sélecteurs).
  • Les classes utilitaires (espaces, polices, couleurs) permettent de gérer très finement les styles et leurs variations. Elles sont une bénédiction.
  • L'outil s'adaptate à différents contextes (responsive, survol/focus, dark mode, etc.).
  • Pourvu d'un écosystème riche (tutoriels, bibliothèques de composants, plugins navigateurs, plugins IDE : VSCode Tailwind Intellisense, etc.)
  • Impose le même environnement et méthodologie pour tout le monde.

Les inconvénients de Tailwind

Sans surprise, TailwindCSS n'est pas exempt de désagréments. Voici une petite énumération que nous avons pu étabir en interne :

  • Lecture du code HTML rendue fouillie et complexe (admettons-le, ça pique les yeux !)
  • Nécessite une rigueur (guidelines) pour ne pas écrire n'importe quoi et dans n'importe quel ordre. Dans Tailwind, nous respectons encore plus l'ordre des propriétés
  • Besoin d'un pense-bête Tailwind, parce que... items-baseline backdrop-invert-0 md:row-start-5 sm:content-around leading-snug dark:tracking-wider 🤷‍♂️
  • Impossible de trouver des éléments précis dans le code (modale, wrapper, navigation, etc.).
  • Inadapté pour les layouts "complexes" (gabarits, grilles non régulières, flexbox, etc.).
  • Inadapté pour pas mal de fonctionnalités (transitions, animations, filtres, transformations)
  • Durée de vie d'un Framework (comparé à la durée de vie de CSS ou d'une méthodologie)
Une capture vidéo d'une longue chaîne de valeurs Tailwind dans une classe CSS et qui nécessite plusieurs secondes de défilement avant d'en atteindre la fin. Notez que les éditeurs ont une option pour passer à la ligne et que cette vidéo se rapproche plus d'un meme que d'un réel argument. (source Twitter)

Pour finir sur ce sujet, un témoignage assez évocateur :

Tailwind peut avoir un impact non négligeable sur certaines performances de chargement lorsqu'il y a de nombreuses répétitions de blocs (exemple vécu sur un projet client dont le menu généré pesait 40 Ko à cause des seules classes Tailwind)

Alors on fait quoi en pratique ?

Nous ne sommes sans doute pas les seuls dans ce cas, mais au quotidien on adapte Tailwind à notre sauce interne :

  • On ajoute des noms "sémantiques" aux composants (pour pouvoir les retrouver, les désigner)
  • On réserve les classes utilitaires pour un usage répétitif (marges, padding, gouttières, couleurs, typo)
  • On s'équipe de plugins pour s'en sortir (ex. Tailwind Intellisense)
  • On a toujours un onglet de navigateur ouvert avec la Doc de Tailwind ou un pense-bête tel que celui de Flowbyte ou Umeshmk

Nous sommes parfaitement conscients que nous n'employons pas ce framework "comme il faudrait" mais plutôt d'une façon hybride, cumulée avec d'autres méthodologies dans lesquelles nous piochons des idées à appliquer.

Vers l'après-Tailwind ?

Nous nous trouvons actuellement dans une phase de transition où l'on s'est remis à tester de nouvelles méthodologies et approches.

En toute transparence, Tailwind CSS ne nous convient pas à 100%. Ou plutôt, cela dépend des types de projets : sur certains projets, il nous paraît naturel de l'employer tandis que sur d'autres typologies il devient contre-productif.

Il est tout à fait probable qu'aucune méthodologie ne nous convienne parfaitement pour l'ensemble de nos prestations d'intégration et seul l'avenir nous le dira.

En attendant, un nouveau challenger est entré en jeu et nous commençons à le tester en production : Cube CSS.

Logo de la méthodologie Cube CSS

CubeCSS n'est pas un framework mais une méthodologie plus générale. Pour le moment, nous la trouvons très prometteuse, mais laissez-nous le temps de l'expérimenter et… de vous partager nos conclusions dans un prochain article !

Publié par Alsacreations.com

Maîtriser la spécificité CSS grâce à Cascade Layers

Les styles CSS doivent leur nom à la Cascade, qui est un bien joli et complexe algorithme tenant compte de nombreux paramètres tels que l'origine des styles, la spécificité des sélecteurs ainsi que leur ordre d'apparence.

La Cascade, c'est l'essence même de CSS. C'est ce qui fait son utilité, sa beauté… et c'est aussi le pire cauchemar des intégratrices et intégrateurs.

La Cascade, c'est ce qui fait que nos paragraphes arboreront une chatoyante couleur hotpink avec les déclarations suivantes :

<p class="kiwi">Coucou !</p>
p {color: tomato;}
p {color: hotpink;}

Mais quel est le problème au juste ?

Dans un monde idéal, l'intégration HTML / CSS est prise en compte en amont du projet, en se donnant les ressources adéquates et les compétences nécessaires. Ce n'est bien évidemment pas un domaine que l'on traite à la légère, après le budget alloué à la communication, au SEO, aux développements "lourds" etc. en se disant que "après tout ce n'est que du CSS, même pas un vrai langage".

Mais ça c'est dans un monde idéal.

Dans un vrai projet, on récupère du code produit par des anciens stagiaires disparus ou par des frameworks de version obsolète, on a totalement oublié de s'appuyer sur une convention de nommage, on intègre avec un onglet de Stackoverflow toujours ouvert dans un coin, et on peste contre le monde entier à tenter d'écraser des styles avec !important (voire des !veryimportant ah non ça c'était une blague).

Qui n'a jamais utilisé !important me jette la première <br> !

Le vrai projet, c'est celui où l'on passe toujours trop de temps à comprendre pourquoi nos paragraphes sont chocolate et pas hotpink dans ce cas là :

p {color: tomato;}
.kiwi {color: pink;}
div p:first-child {color: chocolate;}
p.kiwi {color: hotpink;}

Où les Cascade Layers entrent en jeu

Les spécifications CSS ont introduit une règle-at @layer permettant de redéfinir l'ordre et la priorité dans la cascade CSS à l'aide de "layers" (couches).

Le principe général est simple : l'ordre de déclaration des layers (Layers Order) est prioritaire sur la spécificité des sélecteurs.

Ainsi, dans l'exemple qui suit, les paragraphes prendront la couleur hotpink car leur couche est déclarée en dernier.

@layer reset {
  p {color: olive;}
  .kiwi {color: pink;}
  div p:first-child {color: chocolate;}
}
@layer base {
  p {color: hotpink;}
}

Un tiramisu pour représenter les couches

Déclarer des layers

Il existe plusieurs moyens de créer des couches de styles : la règle @layer avec styles associés, la même sans styles, ou l'import de fichiers de styles externes.

Règle @layer avec styles associés

On déclare la couche via @layer en lui donnant un nom (optionnel) et on y applique des règles CSS.

@layer reset {
  /* ici les règles CSS de la couche reset */
}
@layer base {
  /* ici les règles CSS de la couche base */
}

Règle @layer sans styles

On déclare la couche via @layer vide.

@layer reset;
@layer base;

Pour info, cet exemple est équivalent à cette syntaxe :

@layer reset, base;

Cette formulation, sans styles associés, n'a d'autre but que de déclarer un ordre précis dans les couches.

Import de styles externes

La notion de layer peut être associée à la règle @import :

@import url("reset.css") layer(reset);

En plus de l'import via @import, le W3C travaille sur une version importée avec l'élément <link>.

Concrètement cela représente un moyen très simple de pouvoir :

  • Importer les styles d'un framework tel que Boostrap (au hasard) au sein de layers, donc avec une spécificité moindre
  • Pouvoir redéfinir ses propres styles sans se soucier du poids des sélecteurs Boostrap ni même des !important… et si j'évoque ce framework en particulier, ce n'est pas tout à fait anodin (oui, il y a bien 1307 !important dans ce seul fichier CSS)
@import url("bootstrap.css") layer(framework);

Cake sucré de marque Layers of Joy

En pratique

Le postulat de base est que chaque layer est prioritaire sur le layer déclaré avant lui. L'ordre est donc primordial.

Dans l'exemple qui suit, tous les paragraphes seront de couleur hotpink même s'ils disposent de la classe .kiwi car le layer base est déclaré après reset :

@layer reset {
  p.kiwi {color: tomato;}
}
@layer base {
  p {color: hotpink;}
}

Étendre des styles aux layers existants

Il est possible d'ajouter des styles à une couche existante, simplement en reprenant le nom du layer déjà créé :

@layer reset {
  p.kiwi {color: tomato;}
}
@layer base {
  p {color: hotpink;}
  h1 {color: olive;}
}
@layer reset {
  h1 {color: chocolate;}
}

Dans cet exemple, les styles sur h1 sont ajoutés à ceux déjà présents dans la couche reset.

Notez qu'étendre des layers ne modifie pas l'ordre originel (et donc l'application) des layers. En clair, ici la couleur des titres h1 sera olive car le layer base est déclaré après reset.

Une bonne pratique consiste à définir dans un premier temps l'ordre de toutes les couches en une règle raccourcie (ex. @layer reset, base;), puis d'étendre les styles de chacun des layers, ainsi il n'est pas possible de se tromper dans l'ordre d'application des couches.

Dans l'exemple suivant, je souhaite prioriser les styles de la couche base, je vais donc commencer par indiquer l'ordre à respecter.

Ici, malgré ma maladresse (j'ai étendu les styles base puis ceux de reset), ce sont bien les styles de base qui sont prioritaires et s'appliquent. Les paragraphes seront hotpink :

@layer reset, base;

@layer base {
  p {color: hotpink;}
}

@layer reset {
  p {color: olive;}
  .kiwi {color: tomato;}
}

Cake sucré de marque Multi Layers

Particularité des styles sans layer

Cela peut paraître curieux, mais les styles sans layer sont appliqués en priorité.

Ainsi, dans l'exemple qui suit les paragraphes seront de couleur hotpink :

p {color: hotpink;}

@layer reset {
  p {color: olive;}
  p.kiwi {color: tomato;}
}

Imbrication de layers

On peut imbriquer les Cascade Layers de cette manière :

@layer framework {
  @layer reset {

  }
}

Il est ensuite possible d'étendre les styles de ce layer en y faisant référence ainsi :

@layer framework.reset {
  /* j'étends les styles dans le layer reset dans framework */
  p { color: hotpink; }
}

Compatibilité et mot de la fin

Bonne nouvelle : CSS Cascade Layers est une spécification dont le support est relativement large. À l'heure où cet article est rédigé, seules les anciennes versions de Safari et Samsung Internet sont à la traîne (si on ne tient pas compte d'Internet Explorer bien sûr). Cela signifie que cette fonctionnalité peut très vite être utilisable en production.

Support de CSS Cascade Layer sur les navigateurs web à partir de caniuse.com

Et vous, qu'en pensez-vous ? Êtes-vous aussi impatient que moi de pouvoir bénéficier de cette spécification afin d'assainir radicalement tous les anciens projets web que l'on maintient tant bien que mal ?

Publié par Alsacreations.com

Le Reset CSS, une histoire d'héritage et de réinitialisation

Un Reset CSS c'est quoi ? Pour quoi ?

Les navigateurs web sont tenus d'appliquer des styles par défaut (User Agent Stylesheet) à chaque page HTML, sinon le document afficherait une page blanche (source : specifications CSS).

Chaque navigateur applique ses propres styles par défaut pouvant se révéler différents les uns des autres, sinon ce serait trop facile pour nous autres développeuses et développeurs !

Deux méthodes permettent de contourner ces différences d'affichages entre les navigateurs : le Reset CSS et le Normalize CSS.

Différence de rendu entre les navigateurs web (source)

Reset ou Normalize ?

Un "Reset CSS" (réinitialisation) est une technique qui consiste à repasser toutes les valeurs des propriétés CSS à leur état initial afin repartir de zéro et d'une base vierge avant d'appliquer nos propres styles.

Un "Normalize CSS" (harmonisation) consiste à appliquer des styles de base cohérents identiques sur chaque navigateur. Cette méthode corrige également les différences d'interprétation des spécifications et d'affichage sur l'ensemble des navigateurs et produit une base de travail suffisante (typographie, interlignages, marges, etc.)

Cela dépend bien évidemment de vos besoins, mais dans la plupart des projets il est suffisant d'appliquer une couche de Normalisation, car un Reset vous forcera à redéfinir toutes les propriétés une à une pour votre projet.

L'article (en anglais) Normalize CSS or CSS Reset?! de Elad Shechter résume bien cette dualité d'outils.

L'article (en anglais aussi) A tale of CSS Resets and Everything You Need to Know About Them. Revisited. de Margo Roi est extrêmement riche en informations sur l'évolution des techniques de Reset / Normalize, leur intérêt dans le détail et propose une liste très complète de l'ensemble des solutions existantes actuellement (Eric Meyer, Yahoo!, Normalize, Sanitize, Reboot, Remedy, etc.)

Comprendre et provoquer l'héritage des propriétés

Une propriété héritable est une propriété qui récupère automatiquement la valeur de son parent. C'est le cas par exemple des propriétés typographiques ou des listes (ex. font-size, color, list-style, etc.).

Une propriété non héritable adoptera par défaut la valeur initial telle que définie dans les Spécifications. Par exemple une bordure, une largeur de boîte ou une couleur de fond n'est pas transmise par héritage aux descendants. La majorité des propriétés CSS ne sont pas héritables.

Voici une liste des propriétés CSS héritables courantes :

  • color
  • cursor
  • direction
  • font-family
  • font-size
  • font-style
  • font-variant
  • font-weight
  • font
  • letter-spacing
  • line-height
  • list-style-image
  • list-style-position
  • list-style-type
  • list-style
  • text-align
  • text-indent
  • text-transform
  • visibility
  • white-space
  • word-spacing
Propriété héritée ou non ? (source)

Il est possible de forcer l'héritage d'une propriété en lui appliquant la valeur inherit. Elle prendra alors la valeur de la propriété du parent (c'est à dire son ancêtre direct).

Il est également possible de contrôler l'héritage de toutes les propriétés grâce à la propriété raccourcie all afin d'appliquer la valeur indiquée sur toutes les propriétés (source : MDN : Héritage).

Cibler toutes les propriétés via all

La propriété all est un super-raccourci de toutes les propriétés appliquables à un élément, ce qui lui permet d'hériter ou de réinitialiser toutes les valeurs à la fois.

Les valeurs possibles sont inherit, initial, unset et revert.

Exemple :

.parent {
  display: grid;
  margin: 2rem;
  color: hotpink;
}
.enfant {
  all: inherit; 
}

Dans cet exemple, all cible toutes les propriétés de .enfant et inherit les rend héritables, donc cela est équivalent au code suivant (pour les propriétés concernées) :

.enfant {
  display: grid;
  margin: 2rem;
  color: hotpink;
}

Appliquer les valeurs par défaut via initial

La valeur initial appliquée à une propriété lui confère sa valeur par défaut telle que prévue par les Spécifications CSS.

Exemple :

.parent {
  display: grid;
  margin: 2rem;
  color: hotpink;
}
.enfant {
  all: initial; 
}

Cet exemple est équivalent au code suivant (pour les propriétés concernées) :

.enfant {
  display: inline;
  margin: 0;
  color: canvastext;
}

Ce résultat peut surprendre dans la mesure où l'on s'attend généralement à la valeur block pour la propriété display (surtout si l'enfant est un élément tel que <div> ou <p>) ou black pour la propriété color, or il n'en est rien.

Il faut comprendre que toutes les propriétés CSS ont une valeur initiale définie dans les Spécifications (exemple display vaut inline). Puis elle est parfois écrasée par le navigateur au cas par cas selon les éléments (exemple div {display: block})

Quelques exemples de valeurs initial surprenantes :

  • display: initial; vaut inline
  • max-width: initial; vaut none
  • width: initial; vaut auto
  • position: initial; vaut static
  • cursor: initial; vaut auto
  • appearance: initial; vaut none
  • color: initial; vaut canvastext (valeur récente, adaptée aux modes utilisateur Dark et Light).

Réinitialiser ou hériter via unset

La valeur unset appliquée à une propriété lui confère la valeur du parent si la propriété peut être héritée ou la valeur initiale dans le cas contraire.

Exemple :

.parent {
  display: grid;
  margin: 2rem;
  color: hotpink;
}
.enfant {
  all: unset; 
}

Cet exemple est équivalent au code suivant (pour les propriétés concernées) :

.enfant {
  display: inline;
  margin: 0;
  color: hotpink;
}

Dans cet exemple, display et margin ne sont pas héritables donc leur valeur est calculée à initial, mais la couleur, héritable, est transmise.

Quelques exemples de valeurs unset :

  • max-width: unset; : non héritable donc unset vaut initial qui vaut none
  • width: unset; : non héritable donc unset vaut initial qui vaut auto
  • position: unset; : non héritable donc unset vaut initial qui vaut static
  • font-size: unset; : héritable donc récupère la valeur du parent
  • color: unset; : héritable donc récupère la valeur du parent (ici hotpink)

Friendly reminder: if you don't need IE/Edge support (e.g. internal tools), you don't have to reset every CSS property individually.

button.lame {
padding: 0;
border: none;
outline: none;
font: inherit;
color: inherit;
background: none
}

button.cute {
all: unset
}

— Benjamin De Cock (@bdc) April 24, 2018
Tweet de Benjamin de Cock illustrant l'intérêt de la valeur `unset` appliquée sur un bouton

Récupérer la valeur du navigateur via revert

La valeur revert récupère la valeur appliquée par l'Agent Utilisateur.
S'il n'y en a pas, revert devient unset (qui lui-même vaut inherit ou initial selon le cas 🤯)

Exemple :

.parent {
  display: grid;
  margin: 2rem;
  color: hotpink;
}
.enfant {
  all: revert; 
}

Cet exemple est équivalent au code suivant (pour les propriétés concernées) :

.enfant {
  display: block;
  margin: 0;
  color: hotpink;
}

Dans cet exemple, display vaudra block puisque c'est la valeur attribuée par le navigateur sur l'élément <div>. Le navigateur n'applique pas de valeurs spécifiques au propriétés margin et color donc elles deviendront respectivement initial et inherit.

Quelques exemples de valeurs revert selon l'élément appliqué :

  • div {display: revert} vaut block
  • p {display: revert} vaut block
  • span {display: revert} vaut inline
  • td {display: revert} vaut table-cell
  • input {display: revert} vaut inline-block

Ces valeurs conférées par les navigateurs (User Agent Stylesheet) présentent parfois quelques différences selon les moteurs de rendu.

L'inspecteur d'élément de votre navigateur (clic droit > Inspecter) permet lui aussi d'afficher les valeurs CSS qu'il applique.

Vers le Reset / Normalize parfait ?

L'ensemble des propriétés et valeurs décrites au sein de cet article (all, inherit, initial, unset et revert) offre une très large panoplie de possibilités de réinitialisation en CSS lorsque cela vous est nécessaire.

Un Reset CSS proche de la perfection ressemblerait à ces deux simples lignes :

* {
  all: unset;
  display: revert;
}

Ces deux lignes résument à elles seules les consignes suivantes :

  • Toutes les propriétés appliquables à tous les éléments doivent être remises à zéro, sauf pour les propriétés héritables qui sont conservées, cela évite de devoir tout re-styler notamment pour les propriétés typographiques.
  • Concernant la propriété display, nous souhaitons que la navigateur continue d'appliquer ses valeurs préférées (donc block, inline, etc. selon l'élément).

Si la perfection existait, nous serions en présence du Reset ultime. Mais dans la vraie vie il y a aussi des images, des vidéos, des iframes, des tableaux, des SVG, etc.

Voilà pourquoi je ne saurais que vous conseiller de vous intéresser au projet "The New CSS Reset" maintenu sur Github par l'excellent Ahmad Shadeed et qui apporte les quelques affinages nécessaires.

The New CSS Reset

Et vous, quelle est votre position par rapport à ces problématiques d'héritages, de cascades et de reset CSS ?

Avez-vous des solutions à tout faire que vous nous recommanderiez ?

Publié par Alsacreations.com

❌
❌