Créer son Framework CSS › Les Composants
Boutons, boîtes d'alerte, cartes... Découvrez comment créer des composants d'interface, essentiels pour un design system cohérent.
Qu’est-ce qu’un composant ?
Un composant est une unité autonome et réutilisable qui permet de structurer et de styliser des éléments de manière modulaire dans une application ou un site web.
Les composants facilitent la maintenance et l’évolution d’un projet en permettant la réutilisation de styles et de comportements identiques à différents endroits d’une interface.
Composants pour notre framework
Boutons
Incontournables sur la plupart des interfaces, les boutons contribuent à ajouter de l’interactivité en permettant aux utilisateurs de déclencher des actions en les cliquant ou touchant (sur mobile).
Parmi ces actions, on retrouve généralement :
- La navigation
- La soumission d’un formulaire
- L’affichage d’une popup
- Etc.
Essentiels pour l’expérience utilisateur, ils peuvent être déclinés en plusieurs styles (type, couleurs…) pour s’adapter à différentes utilisations et offrir une hiérarchie visuelle dans les actions proposées.
Par défaut, un bouton aura un style « plein », autrement dit seul son fond sera coloré. ce style sera défini au sein de la classe .btn.
<a class="btn" href="#">.btn</a>On souhaiterait obtenir le résultat suivant :
.btnAfin d’optimiser le contraste des boutons sur l’interface, nous définirons la couleur par défaut comme étant la couleur dark.
:root {
--btn-color: var(--dark-2);
--btn-color-contrast: var(--light-2);
}--btn-colorest la couleur du bouton--btn-color-contrastest la couleur de texte assurant un bon contraste pour les boutons pleins
Vient le moment de définir la classe de base .btn :
/* VARIABLES ... */
.btn {
border-radius: calc(var(--base-unit) / 4);
background-color: var(--btn-color);
color: var(--btn-color-contrast);
cursor: pointer;
display: inline-block;
padding: calc(var(--base-unit) * 0.625) calc(var(--base-unit) * 1);
border-style: solid;
border-width: 1px;
border-color: var(--btn-color);
outline: none;
}Dans notre framework nous créerons des boutons déclinés selon 2 caractéristiques :
- Le type (plein ou en contour)
- La couleur (
primary,success,warningetdanger)
Il est donc temps de créer quelques « modificateurs d’état ».
Type
La class .is-outline rendra notre bouton plus discret, dont seuls le texte et la bordure seront de couleur.
Voici le code de la classe de modificateur d’état .is-outline :
/* VARIABLES ... */
/* .btn ... */
.btn.is-outline {
color: var(--btn-color);
border-color: var(--btn-color);
background-color: transparent;
}
.btn.is-outline:hover {
color: var(--btn-color-contrast);
background-color: var(--btn-color);
}La pseudo classe :hover nous permet ici de transformer visuellement notre bouton de contour en bouton plein lorsqu’il est survolé.
Il est désormais possible de créer des boutons dans le HTML de la manière suivante :
<a class="btn is-outline" href="#">.btn .is-outline</a>On obtient le résultat suivant :
.btn .is-outlineCouleur
Sur cette base de style, plein ou en contour, il serait intéressant d’avoir des classes permettant de modifier la couleur du bouton.
.is-primarypour appliquer la couleur principale.is-success,.is-warninget.is-dangerpour appliquer les couleurs d’état
Plutôt que de redéfinir pour chaque classe de couleur les propriétés CSS, nous allons procéder de manière plus propre, en redéfinissant simplement les variables --btn-color et --btn-color-contrast lorsque la class .btn est utilisée conjointement avec les classes .is-primary, .is-success, .is-warning et .is-danger.
/* VARIABLES */
/* .btn */
/* .is-outline */
.btn.is-primary {
--btn-color: var(--primary);
--btn-color-contrast: var(--primary-contrast);
}
.btn.is-success {
--btn-color: var(--success);
--btn-color-contrast: var(--success-contrast);
}
.btn.is-warning {
--btn-color: var(--warning);
--btn-color-contrast: var(--warning-contrast);
}
.btn.is-danger {
--btn-color: var(--danger);
--btn-color-contrast: var(--danger-contrast);
}Il est désormais possible de créer des boutons de couleur dans le HTML de la manière suivante :
<!-- 🌓 DEFAULT -->
<a class="btn" href="#">.btn</a>
<!-- ✨ PRIMARY -->
<a class="btn is-primary" href="#">.btn .is-primary</a>
<!-- ✅ SUCCESS -->
<a class="btn is-success" href="#">.btn .is-success</a>
<!-- ⚠️ WARNING -->
<a class="btn is-warning" href="#">.btn .is-warning</a>
<!-- ❌ DANGER -->
<a class="btn is-danger" href="#">.btn .is-danger</a>On obtient le résultat suivant :
Les cartes sont des conteneurs visuels permettant de présenter un contenu de manière structurée et attrayante dans un format compact et cohérent, facilement lisible pour l’utilisateur.
Les cartes regroupent divers éléments comme des :
- Médias (images, icônes, vidéos…)
- Textes (titre, description…)
- Eléménts d’action (boutons, liens…)
Elles sont généralement utilisées pour afficher des « previews » de contenus tels que des :
- Article de blog
- Produit ecommerce
- Profils utilisateurs
- Publications sur un réseau social
- Etc.
Chaque carte peut ainsi avoir plusieurs configurations pour s’adapter au contexte et aux données qu’elle contient.
Dans notre framework nous créerons des cartes structurées en 2 parties :
- L’en-tête : contenant une image
- Le corps : contenant un titre, un texte descriptif, des boutons, des liens…
La présentation de nos cartes pourra varier selon 2 formats : vertical (par défaut) et horizontal.
Carte verticale
Notre carte de base inclura les éléments principaux et utilisera des classes pour leur stylisation :
.card: la classe principale de la carte, définissant sa structure et ses marges.card-header: pour l’en-tête de la carte (contenant une image illustrative).card-body: pour le corps de la carte.card-title: pour le titre de la carte.card-description: pour du texte descriptif.card-actions: pour les actions (boutons, liens…), souvent situées en bas de la carte
<div class="card">
<header class="card-header">
<img
src="..."
alt="Description de l'image"
/>
</header>
<div class="card-body">
<h3 class="card-title">Titre</h3>
<p class="card-description">Lorem ipsum dolor sit amet...</p>
<div class="card-actions">
...
</div>
</div>
</div>On souhaite obtenir le résultat suivant :
Titre
Lorem ipsum dolor sit amet…
On souhaite que nos cartes puissent se détacher visuellement de la couleur de fond de l’interface. Pour cela on définira deux variables --card-color-background et --card-color-text.
:root {
--card-color-background: var(--light-3);
--card-color-text: var(--dark-2);
}Le body étant défini par notre fichier 📄 _reset.css à la valeur de notre couleur --light-2, --card-color-background: var(--light-3) permet ici de faire ressortir les cartes en étant légèrement plus claires.
Card container
Créons désormais la class .card, jouant le rôle de conteneur de notre carte.
<div class="card">
...
</div>/* VARIABLES ... */
.card {
background-color: var(--card-color-background);
color: var(--card-color-text);
border-radius: calc(var(--base-unit) / 2);
display: flex;
flex-direction: column;
overflow: hidden;
}- Les couleurs définies préalablement sont appliquées au conteneur.
- Pour maximiser la cohérence globale, les arrondis sont calculés à partir de notre variable de référence
--base-unit. - On empile
.card-headeret.card-bodyavec flex. - On empêche le contenu de la carte de sortir du conteneur avec
overflow: hidden(cela s’avèrera utile pour empêcher l’image de l’en-tête de la carte de sortir des angles arrondis).
Card header
Notre carte intègrera en en-tête (.card-header) la possibilité d’insérer une image.
<div class="card">
<header class="card-header">
<img
src="..."
alt="Description de l'image"
/>
</header>
...
</div>/* VARIABLES ... */
/* .card ... */
.card-header {
overflow: hidden;
}
.card-header > img {
display: block;
width: 100%;
}
.card:hover .card-header > img {
transform: scale(1.1);
}- On demande à l’image d’occuper
100%de l’en-tête de la carte. - Le
display: blockest une astuce permettant de supprimer des espaces blancs potentiels en bas de l’image causés par ledisplay: inlinepar défaut de la balise<img>, surtout si on est dans un contexteflexougrid. - Au survol de la carte toute entière, l’image sera zoomée. On empêche donc l’image de sortir de l’en-tête et d’empiéter sur
.card-bodylors du zoom avecoverflow: hidden.
Card body
Notre carte intègrera dans son corps (.card-body) la possibilité d’insérer un titre, un texte descriptif et des éléments d’actions (boutons, liens…).
<div class="card">
<header class="card-header">
<img
src="..."
alt="Description de l'image"
/>
</header>
<div class="card-body">
<h3 class="card-title">Titre</h3>
<p class="card-description">Lorem ipsum dolor sit amet...</p>
<div class="card-actions">
...
</div>
</div>
</div>/* VARIABLES ... */
/* .card ... */
/* .card-header ... */
.card-body {
padding: calc(var(--base-unit) * 1.5);
display: flex;
flex-direction: column;
gap: var(--base-unit);
align-items: flex-start;
}
.card-title {
font-size: calc(var(--base-unit) * 1.375);
line-height: calc(calc(var(--base-unit) * 1.375) * var(--ratio-line-height));
font-weight: 700;
}
.card-description {
font-size: var(--base-unit);
line-height: calc(var(--base-unit) * var(--ratio-line-height));
font-weight: 400;
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: calc(var(--base-unit) / 2);
align-items: center;
}- Le corps de la carte se détachera des bordures du conteneur avec des marges internes calculées à partir de notre variable de référence
--base-unit. - On empile
.card-title,.card-descriptionet.card-actionsavec flex. .card-titleet.card-descriptionont une taille et une hauteur de ligne calculés à partir de nos variables de référence--base-unitet--ratio-line-heightainsi qu’une graisse spécifique..card-actionspourra accueillir des éléments alignés proprement en ligne.
Carte horizontale
Il pourrait être intéressant de décliner nos cartes au format horizontal. Concrètement, notre image d’en-tête serait située sur la gauche et non en haut du corps de la carte.
Pour créer cette déclinaison nous créerons une classe de modificateur d’état nommée .is-inline.
<div class="card is-inline">
...
</div>Ce modificateur contribuera à adopter plusieurs modifications.
D’abord, on conditionne la flex-direction au type de carte :
.card {
/* ... */
flex-direction: column;
}
.card:not(.is-inline) {
flex-direction: column;
}
.card.is-inline {
flex-direction: row;
}Ensuite, on fait en sorte que si la carte est horizontale, l’image d’en-tête occupe 25% de la largeur et toute la hauteur de la carte :
.card.is-inline .card-header {
max-width: 25%;
}
.card.is-inline .card-header > img {
height: 100%;
object-fit: cover;
}
.card.is-inline .card-body {
align-self: center;
}align-self: center permet de centrer .card-body, dans le cas où l’image est plus haute.
Enfin, on ajoute un modificateur, applicable sur .card-header afin de modifier le ratio (par défaut 25%) que doit occuper l’image.
<div class="card is-inline">
<header class="card-header is-1/3">...</header>
<div class="card-body">...</div>
</div>.card.is-inline .card-header.is-1\/2 {
max-width: calc(100% / 2);
}
.card.is-inline .card-header.is-1\/3 {
max-width: calc(100% / 3);
}
.card.is-inline .card-header.is-1\/4 {
max-width: calc(100% / 4);
}
.card.is-inline .card-header.is-1\/5 {
max-width: calc(100% / 5);
}Le fait de travailler en divisant 100% permet d’obtenir des valeurs exactes. C’est par exemple utile pour éviter d’arrondir 1/3 à 33%.
On obtient alors le résultat suivant :
Titre
Lorem ipsum dolor sit amet…
Alertes
Les boîtes d’alerte jouent un rôle clé pour donner du feedback aux utilisateurs.
Elles informent les utilisateurs, généralement à la suite d’interactions avec l’interface. Ces interactions se traduisent par exemple par :
- Le signalement d’erreurs lors de la saisie de données dans un formulaire
- La confirmation du succès d’une action (création, modification ou suppression d’une ressource…)
- Etc.
La présence de ces boîtes n’est pas systématiquement le fruit d’interactions utilisateur et peuvent être présentes dès le chargement de la page pour par exemple :
- La fourniture d’informations contextuelles pour guider l’utilisateur
- Avertir/mettre en garde l’utilisateur (mises à jour nécessaires, valider son inscription par email, changement de politique tarifaire…)
En communiquant ces informations, les boîtes d’alerte contribuent à améliorer l’expérience globale de l’utilisateur.
Essentielles pour l’expérience utilisateur, elles peuvent être déclinées en plusieurs couleurs, selon le type de message à transmettre (erreur, attention, succès… cf. les couleurs d’état).
Par défaut, une boîte d’alerte sera définie au sein de la classe .alert.
<div class="alert">.alert</div>On souhaiterait obtenir le résultat suivant :
Afin d’optimiser le contraste des boîtes d’alerte sur l’interface, nous définirons la couleur par défaut comme étant la couleur dark.
:root {
--alert-color: var(--dark-2);
--alert-color-contrast: var(--light-2);
}--alert-colorest la couleur de la boîte d’alerte--alert-color-contrastest la couleur de texte assurant un bon contraste
Ensuite, vient le moment de définir la classe de base .alert :
/* VARIABLES ... */
.alert {
padding: calc(var(--base-unit) * 1.5);
border-radius: calc(var(--base-unit) / 2);
background-color: var(--alert-color);
color: var(--alert-color-contrast);
}Comme pour les boutons, nous souhaitons décliner nos boîtes selon les couleurs de notre identité graphique : primary, success, warning et danger.
Il est donc temps de créer quelques « modificateurs d’état ».
Couleur
Sur cette base de style, il serait intéressant d’avoir des classes permettant de modifier la couleur des boîtes d’alerte.
.is-primarypour appliquer la couleur principale.is-success,.is-warninget.is-dangerpour appliquer les couleurs d’état
Plutôt que de redéfinir pour chaque classe de couleur les propriétés CSS, nous allons procéder de manière plus propre, en redéfinissant simplement les variables --alert-color et --alert-color-contrast lorsque la classe .alert est utilisée conjointement avec les classes .is-primary, .is-success, .is-warning et .is-danger.
/* VARIABLES */
/* .alert */
.alert.is-primary {
--alert-color: var(--primary);
--alert-color-contrast: var(--primary-contrast);
}
.alert.is-success {
--alert-color: var(--success);
--alert-color-contrast: var(--success-contrast);
}
.alert.is-warning {
--alert-color: var(--warning);
--alert-color-contrast: var(--warning-contrast);
}
.alert.is-danger {
--alert-color: var(--danger);
--alert-color-contrast: var(--danger-contrast);
}Il est désormais possible de créer des boîtes d’alerte de couleur dans le HTML de la manière suivante :
<!-- 🌓 DEFAULT -->
<div class="alert">.alert</div>
<!-- ✨ PRIMARY -->
<div class="alert is-primary">.alert .is-primary</div>
<!-- ✅ SUCCESS -->
<div class="alert is-success">.alert .is-success</div>
<!-- ⚠️ WARNING -->
<div class="alert is-warning">.alert .is-warning</div>
<!-- ❌ DANGER -->
<div class="alert is-danger">.alert .is-danger</div>On obtient le résultat suivant :
Code complet
Voici le code complet des fichiers 📄 _button.css, 📄 _alert.css et 📄 _card.css ainsi que du fichier global 📄 all.css :
:root {
--btn-color: var(--dark-2);
--btn-color-contrast: var(--light-2);
}
.btn {
border-radius: calc(var(--base-unit) / 4);
background-color: var(--btn-color);
color: var(--btn-color-contrast);
cursor: pointer;
display: inline-block;
padding: calc(var(--base-unit) * 0.625) calc(var(--base-unit) * 1);
border-style: solid;
border-width: 1px;
border-color: var(--btn-color);
outline: none;
}
.btn.is-outline {
color: var(--btn-color);
border-color: var(--btn-color);
background-color: transparent;
}
.btn.is-outline:hover {
color: var(--btn-color-contrast);
background-color: var(--btn-color);
}
.btn.is-primary {
--btn-color: var(--primary);
--btn-color-contrast: var(--primary-contrast);
}
.btn.is-success {
--btn-color: var(--success);
--btn-color-contrast: var(--success-contrast);
}
.btn.is-warning {
--btn-color: var(--warning);
--btn-color-contrast: var(--warning-contrast);
}
.btn.is-danger {
--btn-color: var(--danger);
--btn-color-contrast: var(--danger-contrast);
}:root {
--alert-color: var(--dark-2);
--alert-color-contrast: var(--light-2);
}
.alert {
padding: calc(var(--base-unit) * 1.5);
border-radius: calc(var(--base-unit) / 2);
background-color: var(--alert-color);
color: var(--alert-color-contrast);
}
.alert.is-primary {
--alert-color: var(--primary);
--alert-color-contrast: var(--primary-contrast);
}
.alert.is-success {
--alert-color: var(--success);
--alert-color-contrast: var(--success-contrast);
}
.alert.is-warning {
--alert-color: var(--warning);
--alert-color-contrast: var(--warning-contrast);
}
.alert.is-danger {
--alert-color: var(--danger);
--alert-color-contrast: var(--danger-contrast);
}:root {
--card-color-background: var(--light-3);
--card-color-text: var(--dark-2);
}
.card {
background-color: var(--card-color-background);
color: var(--card-color-text);
border-radius: calc(var(--base-unit) / 2);
display: flex;
overflow: hidden;
}
.card:not(.is-inline) {
flex-direction: column;
}
.card-header {
overflow: hidden;
}
.card-header > img {
display: block;
width: 100%;
}
.card:hover .card-header > img {
transform: scale(1.1);
}
.card-body {
padding: calc(var(--base-unit) * 1.5);
display: flex;
flex-direction: column;
gap: var(--base-unit);
align-items: flex-start;
}
.card-title {
font-size: calc(var(--base-unit) * 1.375);
line-height: calc(calc(var(--base-unit) * 1.375) * var(--ratio-line-height));
font-weight: 700;
}
.card-description {
font-size: var(--base-unit);
line-height: calc(var(--base-unit) * var(--ratio-line-height));
font-weight: 400;
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: calc(var(--base-unit) / 2);
align-items: center;
}
.card.is-inline {
flex-direction: row;
}
.card.is-inline .card-header {
max-width: 25%;
}
.card.is-inline .card-header > img {
height: 100%;
object-fit: cover;
}
.card.is-inline .card-header.is-1\/2 {
max-width: calc(100% / 2);
}
.card.is-inline .card-header.is-1\/3 {
max-width: calc(100% / 3);
}
.card.is-inline .card-header.is-1\/4 {
max-width: calc(100% / 4);
}
.card.is-inline .card-header.is-1\/5 {
max-width: calc(100% / 5);
}
.card.is-inline .card-body {
align-self: center;
}@import '_card.css';
@import '_button.css';
@import '_alert.css';Les composants du framework sont importés dans 📄 app.css :
@import 'config.css';
@import 'base/all.css';
@import 'layout/all.css';
@import 'components/all.css';
@import 'utilities/all.css';