Convention CSS - noelno/dovelei GitHub Wiki

Convention perso inspirée de nombreuses méthodes populaires.

Méthodologie

Cette convention est principalement basée sur cssguidelin.es, elle-même en partie inspirée des méthodologies SMACSS, BEM et OOCSS.

OOCSS

Plusieurs principes phares repris de l'OOP, basés sur l'idée de représenter un élément de la page comme un objet.
Cet objet peut être :

  • objet média - boîte contenant un média de taille fixe (image ou vidéo) et un texte d'accompagnement de taille variable.
    Ex : commentaire, présentation de l'auteur d'un article
  • objet module - boîte générique pouvant contenir un header, un contenu principal et un footer
  • et encore plein d'autres : https://github.com/stubbornella/oocss/tree/master/oocss/src/components

On retiendra les notions suivantes :

  • étendre un objet via la composition plutôt que par l'héritage

  • factoriser des propriétés communes

  • séparer le style de la boîte du style du texte

      .box {
          display: block;
          padding: 10px;
      }
    
      .message {
          border-style: solid;
          border-width: 1px 0;
          font-weight: bold;
      }
    
      .message--error {
          background-color: #fee;
          color: #f00;
      }
    
      .message--success {
          background-color: #efe;
          color: #0f0;
      }
    

Côté HTML on aura un message d'erreur contenu dans

On compose notre objet en associant les caractéristiques de différentes classes. Si on avait créé un élément avec uniquement
, on aurait eu à surcharger .message--error pour qu'il hérite aussi des styles de .box et de .message, ce qui peut potentiellement entraîner des problèmes tels que des duplications de code inutiles ou des surcharges en cascade.

BEM

Avec la notation BEM on décompose les éléments en 3 catégories :

  • Bloc. Composant de la page, l'équivalent de l'objet OOCSS : champs, boutons, menus, formulaires, objet média…
  • Element. Composant qui ne peut exister que dans le contexte d'un Bloc : entrée de menu, élément de liste…
  • Modificateur. Déclinaison du bloc ou de l'élément, son nom décrit généralement son apparence, son comportement, son état.

L'exemple précédent du message d'erreur est écrit en notation BEM. .message est le bloc, .message--error est un modificateur, et on aurait pu avoir .box__close comme élément, pour représenter la croix sur laquelle cliquer pour fermer le message par exemple.

SMACSS

Cf. SMACSS

Base : html, body, a, a.hover + reset Layout : grille et liste Module : composant (.nav, .alert, .btn, .modal) State : is-active, is-inactive, is-expanded, is-hidden, pseudo-classes (:hover, :focus) Thème : déclinaison par page ou section

La philosophie est très proche de BEM. On conservera la structure des fichiers (base, layout, module, état, thème), mais pas forcément toute les conventions de nommage. On garde aussi le principe des thèmes préfixés avec Sass.

Fichiers

Développement

Les déclarations CSS sont fractionnées sur plusieurs fichiers Sass :

Styles de base (_base.scss)

Reset (_reset.scss)

Uniformisation des styles de base des principaux éléments sur les différents navigateurs
Éventuel reset de certains éléments d'interaction

Global (_global.scss)

Configuration

Modèle de boîte (box-sizing), polices (@font-face), taille de départ pour les polices en rem, style de html et de body (texte courant), variables Sass.

La plupart des frameworks définissent aussi un style par défaut pour les principaux éléments de phrasé (titraille, paragraphes, listes, liens, images responsive…) mais je préfère le faire au niveau de chaque module pour éviter d'avoir à les surcharger plus tard.

Classes utilitaires

Classes utiles quand on veut appliquer une seule propriété ou un ensemble de propriétés à un bloc sans surcharger la règle de base. Par exemple .bfc (pour block formatting context) pour créer un contexte de formattage bloc, .dn (pour display: none) pour masquer temporairement un bloc, .visually-hidden pour masquer un bloc tout en le laissant visible aux technologies d'assistance…

Mixins utilitaires

Groupes de propriétes fréquemment utilisés mais qui n'ont pas vocation à apparaître en tant que classe dans le code HTML : clearfix(), constrained()…

@mixin inuit-clearfix() {

  &:after {
	content: "" !important;
	display: block !important;
	clear: both !important;
  }

}

Puis, utilisé dans d'autres règles :

.box {
	@include inuit-clearfix();
}

Structure (_structure.scss)

Au cas par cas j'utilise :

  • _layout.scss pour les projets simples
  • …grille.scss pour les mises en page variées, complexes ou avec beaucoup d'éléments.

Disposition (_layout.scss)

Le style des principales boîtes du layout qui englobent les modules :

  • .header (englobe titre, champ de recherche, réseaux sociaux, menu, slider…)
  • .footer (englobe copyright, liens, widget Twitter…)
  • .sidebar (englobe archives, categories, publicité)
  • .section principale (englobe articles, modules, media…)

Grille (_grille.scss)

Modules (un par fichier)

Tous les composants réutilisables : .media, .box, .mod, .nav, .btn, .modal, .field, .accordion… qui peuvent être placés n'importe où dans le layout + les sous-composants (.modal__text), ses modificateurs (.modal--dismiss), ainsi que d'éventuels style liés à des fonctionnalités Modernizr (no-js, ie…).

Thèmes et adaptations (un par thème)

Déclinaisons du style de base pour une page ou une rubrique.

Impression (_print.scss)

Les styles d'impression auraient leur place dans les styles globaux, mais sont mis en fin de fichier pour des gains de performance.

Fichiers de production

On utilise Grunt pour générer deux à trois feuilles de styles :

  • main.css sur la totalité des pages du site (sauf la page d'accueil s'il existe un home.css)
  • home.css sur la page d'accueil si celle-ci a un style complètement différent du reste du site
  • themes.css sur les pages et rubriques utilisant au moins un thème

J'avais envisagé de séparer les styles no-js et ie de ces feuilles pour gagner en performance, mais le peu de poids économisé au chargement de la feuille de style est repris par la lourde syntaxe qui permet de switcher entre les différentes feuilles.

Ce qui donne sur une page :

<!doctype html>
<html class="no-js" lang="fr">
	<head>        
		<link rel="stylesheet" href="css/main.css">
		<script src="js/vendor/modernizr-2.8.3.min.js"></script>
	</head>
	<body>

Tâches Grunt

  • scss-lint : pour linter le code
  • csscomb : pour réordonner automatiquement les propriétés
  • beautifier : pour faire le ménage dans l'indentation
  • compass : pour la compilation du scss en css et la suppression des commentaires
  • uncss : pour la suppression des règles non utilisées
  • cssmin : pour la minification

Et les frameworks ?

Utiliser un framework, par exemple Bootstrap, revient à utiliser une base, une structure et quelques modules existants.

Bootstrap a un portage Sass et est distribué en .scss, ce qui signifie que l'on peut facilement le customiser sans avoir à modifier le core. Si je veux utiliser la grille Bootstrap j'inclus vendor/bootstrap/scss/_grid.scss dans mon fichier _structure.scss. On peut continuer à utiliser ses propres sélécteurs sémantiques : il suffit d'importer les règles bootstrap dans une règle perso.

Il est aussi possible de modifier des valeurs de base sans modifier le core et sans avoir à le surcharger, en modifiant le fichier ]_bootstrap-variables.scss](https://github.com/twbs/bootstrap-sass/blob/master/templates/project/_bootstrap-variables.sass)

Convention d'écriture

Indentation : 2 espaces
Table des matières, dans la feuille principale (main.scss) :

/**
 * SOMMAIRE
 *
 * SETTINGS
 * Global...............Globally-available variables and config.
 *
 * TOOLS
 * Mixins...............Useful mixins.
 *
 * GENERIC
 * Normalize.css........A level playing field.
 * Box-sizing...........Better default `box-sizing`.
 *
 * BASE
 * Headings.............H1–H6 styles.
 *
 * OBJECTS
 * Wrappers.............Wrapping and constraining elements.
 *
 * COMPONENTS
 * Page-head............The main page header.
 * Page-foot............The main page footer.
 * Buttons..............Button elements.
 *
 * TRUMPS
 * Text.................Text helpers.
 */

Limite de 80 caractères par ligne quand c'est possible.

/**
 * I am a long-form comment. I describe, in detail, the CSS that follows. I am
 * such a long comment that I easily break the 80 character limit, so I am 
 * broken across several lines.
 */

Un saut de ligne entre le bas de l'entête et la première déclaration.
Cinq sauts de lignes à la fin d'une section.
Deux sauts de lignes entre deux règles de même niveau dans la même section. Un saut de ligne entre deux règles ascendant-descendant.

.foo { }

  .foo__bar { }

	.foo__bar--baz { }

	
.foo--baz { }






/**
 * Commentaire
 */

.another-selector { }

Les sélécteurs liés au même module sur la même ligne :

.foo, .foo--bar,
.baz {
  display: block;
  background-color: green;
  color: red;
}

Les règles à une déclaration, si elles font partie d'un groupe de règles similaires, peuvent être déclarées sur une ligne et sans saut de ligne de séparation.

.icon {
  display: inline-block;
  width:  16px;
  height: 16px;
  background-image: url(/img/sprite.svg);
}

.icon--home     { background-position:   0     0  ; }
.icon--person   { background-position: -16px   0  ; }
.icon--files    { background-position:   0   -16px; }
.icon--settings { background-position: -16px -16px; }

Indenter les règles selon le niveau d'imbrication des éléments dans le DOM

.foo { }

  .foo__bar { }

	.foo__bar--baz { }

Aligner les déclarations communes :

.foo {
  -webkit-border-radius: 3px;
	 -moz-border-radius: 3px;
		  border-radius: 3px;
}

.bar {
  position: absolute;
  top:    0;
  right:  0;
  bottom: 0;
  left:   0;
  margin-right: -10px;
  margin-left:  -10px;
  padding-right: 10px;
  padding-left:  10px;
}

Côté HTML : Séparer les classes de deux espaces, Mettre les classes sans rapport sur des lignes distinctes.

<div class="media  media--large
			testimonial  testimonial--main">

Séparer de 5 retours à la ligne les blocs (blocs BEM) et d'une ligne les éléments imbriqués.

Commentaires

Éviter la pollution visuelle en écrivant trop de commentaires, ou des commentaires trop longs (cf. Clean code).

  • clarifier les noms de classe peu parlants (idéalement on n'utilisera pas ou peu de noms peu parlants)

  • éclaircir l'utilisation d'une technique peu courante (par exemple .bfc comme clearfix, ou margin-left: auto pour caler un bloc à droite)

  • signaler les liens peu évidents (styles hérités beaucoup plus haut / légués beaucoup plus bas)

  • à quoi correspondent les valeurs - si ce n'est pas évident, par exemple width(100% - 20px) dont le 20px équivaut à la marge

  • signaler l'importance d'une règle qui a l'air de ne servir à rien, au cas où un autre développeur serait tenté de le supprimer

  • signaler un lien avec une déclaration issue d'un fichier séparée ("surcharge .btn dans _buttons.scss", "Les règles qui suivent étendent .btn")

    // Dimensions of the @2x image sprite: $sprite-width: 920px; $sprite-height: 212px;

    /**

      1. Default icon size is 16px.
      1. Squash down the retina sprite to display at the correct size. / .sprite { width: 16px; / [1] / height: 16px; / [1] / background-image: url(/img/sprites/main.png); background-size: ($sprite-width / 2 ) ($sprite-height / 2); / [2] */ }

Nommage

  • classes ordinaires : en minuscule, mots séparés par un tiret
  • classes modules : .module, .module__souscomposant
  • classes état : .module--etat

BEM

Bloc : composant (champ, bouton, menu…) .bloc (lowerCamelCase) Element : composant qui ne peut exister que dans un Bloc (entrée de menu) .bloc__element (.post-title, .post-date, .post-author, .post-thumb, .post-entry, .modal-header, .modal-body, .modal-footer) Modificateur : caractéristique du composant : apparence, comportement, état. .bloc--modificateur ou .bloc__element--modificateur (--success, --submit)

Bien choisir le nom :

Astuce pour ajouter de la spécificité à une classe sans créer de dépendance avec une autre : l'accoler à elle-même :

.site-nav.site-nav { }

Astuce pour séléctionner via un id tout en conservant la spécificité d'une classe :

[id="third-party-widget"] { }

Pour étendre un objet, rester au même niveau de spécificité :

.box {
        display: block;
        padding: 10px;
}

.box--large {
        padding: 20px;
}

On ajoutera ensuite à l'élément du DOM les deux classes (Composition over heritage)

Ne pas factoriser à outrance : si des règles n'ont rien à voir entre elles à part une déclaration, mieux vaut une répétition qu'un couplage illogique.
Si 2-3 règles réapparaîssent plusieurs fois par simple coincïdence, on peut éventuellement en faire un mixin.

⚠️ **GitHub.com Fallback** ⚠️