Compare commits

..

2 commits

Author SHA1 Message Date
1be9d7f04c
DRAFTS 2025-07-07 00:31:31 +02:00
4da57f71e4
feat(content): add a new article about CSS 2025-07-07 00:30:57 +02:00
3 changed files with 681 additions and 7 deletions

View file

@ -36,6 +36,7 @@ outputFormats:
markup:
highlight:
lineNumbersInTable: false
noClasses: false
goldmark:
parser:

View file

@ -11,7 +11,11 @@ tags:
summary: |
entrer le résumé
description: |
entrer la description
The article explores recent developments in CSS, such as nested rules, CSS
variables, and classless CSS, which allow for cleaner and more efficient
stylesheet writing. It also mentions future features like CSS mixins, custom
CSS properties, and CSS scopes, which promise to further enhance the way user
interfaces are styled.
---
I recently stumbled upon a note on a page of [Plain Vanilla] in which I learned
@ -55,7 +59,6 @@ header {
makes more sense to me than
```css
a {
text-decoration: none;
}
@ -79,7 +82,7 @@ I have a few reasons for this:
to grasp;
* I can move around a group of selector without forgetting a declaration;
* I can use my IDE code folding based on indent to close a group and navigate
long CSS files;
long CSS files.
Since the [CSS Nesting Module] is [Baseline Widely Available] and is supported
by [90% of users], It can be used to write nested CSS. So now, this is a thing in
@ -148,8 +151,8 @@ label {
}
```
I missed the [CSS Custom Properties] module from 2017, which allowed me to write
the same thing in pure CSS:
I missed the [CSS Custom Properties for Cascading Variables Module Level 1]
module from 2017, which allowed me to write the same thing in pure CSS:
```css
:root {
@ -161,16 +164,24 @@ label {
&.error {
color: var(--color-error);
}
&.success {
color: var(--color-success);
}
}
.notification {
&.error {
background-color: var(--color-error);
}
&.success {
background-color: var(--color-success);
}
}
```
[CSS Custom Properties]: https://www.w3.org/TR/css-variables/
[CSS Custom Properties for Cascading Variables Module Level 1]: https://www.w3.org/TR/css-variables/
## Classless CSS
@ -407,6 +418,125 @@ Though in some cases, it can easily be replaced with CSS variables:
[SASS Mixin Guide]: https://sass-lang.com/documentation/at-rules/mixin/#arguments
[CSS Functions and Mixins Module]: https://www.w3.org/TR/2025/WD-css-mixins-1-20250515/
### CSS Custom Properties
This one is a nice little feature from the [CSS Properties and Values API Level
1] module which extends CSS variables nicely.
They allow to define the type, initial value and inheritance rule of a custom
variables. For example:
```css {linenos=true}
@property --my-color {
syntax: "<color>";
inherits: false;
initial-value: black;
}
.primary {
--my-color: red;
}
.secondary {
--my-color: 10px;
}
button {
background-color: var(--my-color);
color: white;
}
```
Here, the definition of `--my-color` on line 12 is not valid (it is a length and
not a color). As the property value is not inherited from a parent, the initial
value will be used: a `<button class="secondary">` will have a black background.
However, as the property is defined to be a color, linters like [Stylelint] and
[ESLint] will eventually be able to catch such errors, in addition to catching
typos in values or in the property name.
[CSS Properties and Values API Level 1]: https://drafts.css-houdini.org/css-properties-values-api
[Stylelint]: https://stylelint.io
[ESLint]: https://eslint.org
### CSS Scopes
### CSS Custom Properties
This one is maybe the one I am expecting the most.
To style a UI component, it is often necessary to target a specific element of
the component, and repeat selectors:
```css
.card { /*...*/ }
.card article { /*...*/ }
.card article header { /*...*/ }
.card article footer { /*...*/ }
.card article footer button { /*...*/ }
```
With nested CSS modules, this can be simplified to:
```css
.card {
/*...*/
article {
/*...*/
header {
/*...*/
}
footer {
/*...*/
button {
/*...*/
}
}
}
}
```
It can however have some edge cases and yield unexpected results (see the
[example on MDN]).
Scopes are a new feature from the [CSS Cascading and Inheritance Level 6]
module. They are a more natural way of defining rules:
```css
@scope (.card){
:scope {
/*...*/
}
article {
/*...*/
header {
/*...*/
}
footer {
/*...*/
button {
/*...*/
}
}
}
}
```
`@scope` power comes from several fact:
* It follows a proximity rules: an element is styled with the nearest scope
rules;
* It adds no specificity to the selector, which means that it can be overridden
more easily;
* it is more expressive.
[CSS Cascading and Inheritance Level 6]: https://drafts.csswg.org/css-cascade-6/
[example on MDN]: https://developer.mozilla.org/en-US/docs/Web/CSS/@scope#how_scope_conflicts_are_resolved

View file

@ -0,0 +1,543 @@
---
# vim: spell spelllang=fr
title: 'Adopter un CSS moderne'
slug: 'adopter-css-moderne'
date: '2025-07-06T14:29:26+02:00'
draft: true
categories:
- dev
tags:
- CSS
summary:
Exploration de développements récents de CSS dont j'étais passé à côté.
description: |
L'article explore les récentes évolutions du CSS, telles que l'imbrication des
règles, les variables CSS, et l'utilisation de CSS sans classes, qui
permettent une écriture plus propre et plus efficace des feuilles de style. Il
mentionne également des fonctionnalités futures comme les mixins CSS, les
propriétés personnalisées CSS, et les portées CSS, qui promettent d'améliorer
encore davantage la manière de styliser les interfaces utilisateur.
---
Je suis récemment tombé sur une note dans une page de [Plain Vanilla] dans
laquelle j'ai appris que le CSS imbriqué est valide. C'est possible depuis assez
longtemps, mais je ne le savais pas (je suis assez en retard dans mon lecteur
RSS).
Cela m'a permis de rattraper certaines des récentes évolutions du CSS.
[Plain Vanilla]: https://plainvanillaweb.com/pages/styling.html
## CSS imbriqué
L'imbrication des règles CSS est l'une des principales raisons pour lesquelles
j'ai utilisé [SASS] pendant 15 ans. J'ai toujours préféré écrire des règles
imbriquées pour regrouper des unités cohérentes de CSS. Par exemple,
```scss
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
header {
nav {
ul {
list-style: none;
li {
text-align: center;
}
}
}
}
```
a plus de sens pour moi que
```css
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header nav ul {
list-style: none;
}
header nav ul li {
text-align: center;
}
```
Il y a plusieurs raisons à ça :
* C'est plus facile à lire car les sélecteurs sont plus courts et la hiérarchie
est plus facile à comprendre ;
* Je peux déplacer un groupe de sélecteurs sans risquer d'oublier une
déclaration ;
* Je peux utiliser le repliement de code basé sur l'indentation de mon IDE pour
fermer un groupe et naviguer dans de longs fichiers CSS.
Depuis que le module [CSS Nesting] est disponible de manière générale et est
[supporté par 90% des utilisateurs], il peut être utilisé pour écrire du CSS
imbriqué. Donc maintenant, c'est du CSS valide :
```css
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
header {
nav {
ul {
list-style: none;
li {
text-align: center;
}
}
}
}
```
Le fichier CSS de ce site a été réécrit en utilisant le CSS imbriqué.
[SASS]: https://sass-lang.com
[CSS Nesting]: https://drafts.csswg.org/css-nesting/
[supporté par 90% des utilisateurs]: https://caniuse.com/css-nesting
[CSS file]: https://bcarlin.net/static/css/bcarlin.css
## Variables CSS
Pour moi, les variables sont essentielles pour assurer une interface utilisateur
cohérente. Elles permettent de réutiliser des couleurs, des tailles, des
espacements, etc.
C'est aussi une raison pour laquelle j'ai utilisé SASS. Cela m'a permis d'écrire
du CSS avec des variables réutilisables :
```scss
$color-error: red;
$color-success: green;
label {
&.error {
color: $color-error;
}
&.success {
color: $color-success;
}
}
.notification {
&.error {
background-color: $color-error;
}
&.success {
background-color: $color-success;
}
}
```
J'ai manqué la publication du module [CSS Custom Properties for Cascading
Variables Module Level 1] de 2017, qui m'a permis d'écrire la même chose en CSS
pur :
```css
:root {
--color-error: red;
--color-success: green;
}
label {
&.error {
color: var(--color-error);
}
&.success {
color: var(--color-success);
}
}
.notification {
&.error {
background-color: var(--color-error);
}
&.success {
background-color: var(--color-success);
}
}
```
[CSS Custom Properties for Cascading Variables Module Level 1]: https://www.w3.org/TR/css-variables/
## CSS Sans Classes
Peut-être que je vais à contre-courant ici, , compte tenu de la popularité des
frameworks CSS « utilitaires » comme [TailwindCSS].
Ce point n'est pas vraiment une fonctionnalité CSS en soi, mais c'est une façon
d'écrire du CSS, où le HTML sémantiquement correct est automatiquement mis en
forme correctement. Dans une certaine mesure, il est cependant soutenu par
certains [Sélecteurs CSS Niveau 4] qui sont maintenant largement implémentés
dans les navigateurs, tels que `:has`, `:is`, `:where`, `:not`, etc.
J'utilisais habituellement [BootstrapCSS] dans mes projets parce qu'il est
complet et facile à utiliser, mais je n'ai jamais aimé la façon dont il imposait
une structure CSS relativement lourde. Pour ce site, je cherchais quelque chose
de plus léger et je suis tombé sur [PicoCSS] qui a mis en forme 90% de mon site
sans changer quoi que ce soit à mes modèles.
J'avais déjà une structure HTML de base sémantiquement significative :
```html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<header>
<nav>
<ul>
<li><a href="/">Accueil</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h1>Titre de la Page</h1>
</header>
<section>
<!-- ... -->
</section>
</article>
</main>
<footer>
<!-- ... -->
</footer>
</body>
</html>
```
Et j'aime vraiment la façon dont cela fonctionne : le contenu est mis en forme
en fonction de son balisage sémantique, et non en fonction d'une structure HTML
imposée.
Par exemple, voici le [composant Modal de Bootstrap] :
```html
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Titre de la fenêtre</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Corps de la fenêtre
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button>
<button type="button" class="btn btn-primary">Enregistrer</button>
</div>
</div>
</div>
</div>
```
Voici le [composant Modal de Tailwind Plus] :
```html
<div>
<button class="rounded-md bg-gray-950/5 px-2.5 py-1.5 text-sm font-semibold text-gray-900 hover\:bg-gray-950/10">Ouvrir le dialogue</button>
<div class="relative z-10" aria-labelledby="dialog-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true"></div>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm\:items-center sm\:p-0">
<div class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm\:my-8 sm\:w-full sm\:max-w-lg">
<div class="bg-white px-4 pt-5 pb-4 sm\:p-6 sm\:pb-4">
<div class="sm\:flex sm\:items-start">
<div class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm\:mx-0 sm\:size-10">
<svg class="size-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
</div>
<div class="mt-3 text-center sm\:mt-0 sm\:ml-4 sm\:text-left">
<h3 class="text-base font-semibold text-gray-900" id="dialog-title">Titre de la fenêtre</h3>
<div class="mt-2">
Corps de la fenêtre
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm\:flex sm\:flex-row-reverse sm\:px-6">
<button type="button" class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover\:bg-red-500 sm\:ml-3 sm\:w-auto">Enregistrer</button>
<button type="button" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-gray-300 ring-inset hover\:bg-gray-50 sm\:mt-0 sm\:w-auto">Fermer</button>
</div>
</div>
</div>
</div>
</div>
</div>
```
Comparez-les avec le [composant Modal de PicoCSS] :
```html
<dialog open>
<article>
<header>
<button aria-label="Fermer" rel="prev"></button>
<p>
<strong>Titre de la fenêtre</strong>
</p>
</header>
Corps de la fenêtre
<footer>
<button class="secondary">Fermer</button>
<button>Enregistrer</button>
</footer>
</article>
</dialog>
```
Cela fait une énorme différence en termes de simplicité, de lisibilité et
d'accessibilité (notez que les attributs ARIA sont rendus inutiles car le
balisage sémantique porte déjà cette information).
[TailwindCSS]: https://tailwindcss.com
[Sélecteurs CSS Niveau 4]: https://drafts.csswg.org/selectors-4/
[BootstrapCSS]: https://getbootstrap.com
[PicoCSS]: https://picocss.com
[composant Modal de Bootstrap]: https://getbootstrap.com/docs/4.3/components/modal/#live-demo
[composant Modal de Tailwind Plus]: https://tailwindcss.com/plus/ui-blocks/application-ui/overlays/modal-dialogs
[composant Modal de PicoCSS]: https://picocss.com/docs/modal
## `@import` pour diviser les fichiers CSS
Un dernièr point pour lequel j'aimais utiliser SASS était la possibilité de
diviser les fichiers CSS en fichiers plus petits pour les rendre plus faciles à
comprendre. Par exemple :
```scss
@use 'reset';
@use 'typography';
@use 'layout';
@use 'content';
```
Avec le module [CSS Cascading and Inheritance Level 5], CSS a cela nativement :
```css
@import url('reset.css');
@import url('typography.css');
@import url('layout.css');
@import url('content.css');
```
De ma compréhension, les fichiers CSS `@import`és sont téléchargés en parallèle,
ce qui réduit le coût d'avoir plusieurs fichiers à télécharger. Les règles
CSS `@import` ont même l'avantage d'être conditionnelles. Par exemple :
```css
@import url("light.css") only screen and (prefers-color-scheme: light);
@import url('dark.css') only screen and (prefers-color-scheme: dark);
```
[CSS Cascading and Inheritance Level 5]: https://drafts.csswg.org/css-cascade-5/
## Les évolutions que j'attends avec impatience
Voici quelques spécifications que j'ai hâte d'utiliser. Je ne les utilise pas
encore en raison du support des navigateurs ou parce que je n'en ai pas encore
eu besoin. Mais je suis impatient de les essayer.
### Mixins CSS
Les Mixins CSS sont également une fonctionnalité majeure de SASS, et favorisent
un code CSS plus propre et plus réutilisable.
CSS les aura avec le [Module des fonctions et mixins CSS], qui est encore un
brouillon, et dans lequel les mixins ne sont pas encore spécifiés.
En attendant, voici un exemple du [Guide des Mixins SASS] :
```scss
@mixin rtl($property, $ltr-value, $rtl-value) {
#{$property}: $ltr-value;
[dir=rtl] & {
#{$property}: $rtl-value;
}
}
.sidebar {
@include rtl(float, left, right);
}
```
Bien que dans certains cas, cela puisse être facilement remplacé par des
variables CSS :
```css
:root {
--sidebar-float: left;
}
[dir=rtl] {
--sidebar-float: right;
}
.sidebar {
float: var(--sidebar-float);
}
```
[Guide des Mixins SASS]: https://sass-lang.com/documentation/at-rules/mixin/#arguments
[Module des fonctions et mixins CSS]: https://www.w3.org/TR/2025/WD-css-mixins-1-20250515/
### Propriétés Personnalisées CSS
Celle-ci est une petite fonctionnalité sympa du module [CSS Properties and
Values API Level 1] qui étend les variables CSS.
Elles permettent de définir le type, la valeur initiale et la règle d'héritage
d'une variable personnalisée. Par exemple :
```css {linenos=true}
@property --my-color {
syntax: "<color>";
inherits: false;
initial-value: black;
}
.primary {
--my-color: red;
}
.secondary {
--my-color: 10px;
}
button {
background-color: var(--my-color);
color: white;
}
```
Ici, la définition de `--my-color` à la ligne 12 n'est pas valide (c'est une
longueur et non une couleur). Comme la valeur de la propriété n'est pas héritée
d'un parent, la valeur initiale sera utilisée : un `<button class="secondary">`
aura un fond noir.
Cependant, comme la propriété est définie pour être une couleur, des linters
comme [Stylelint] et [ESLint] pourront attraper de telles erreurs, en plus
d'attraper les fautes de frappe dans les valeurs ou dans le nom de la propriété.
[CSS Properties and Values API Level 1]: https://drafts.css-houdini.org/css-properties-values-api
[Stylelint]: https://stylelint.io
[ESLint]: https://eslint.org
### Scopes CSS
Celle-ci est peut-être celle que j'attends le plus. Pour styliser un composant
d'interface utilisateur, il est souvent nécessaire de cibler un élément
spécifique du composant, et de répéter les sélecteurs :
```css
.card { /*...*/ }
.card article { /*...*/ }
.card article header { /*...*/ }
.card article footer { /*...*/ }
.card article footer button { /*...*/ }
```
Avec les modules CSS imbriqués, cela peut être simplifié en :
```css
.card {
/*...*/
article {
/*...*/
header {
/*...*/
}
footer {
/*...*/
button {
/*...*/
}
}
}
}
```
Cela peut cependant avoir quelques cas particuliers et donner des résultats
inattendus (voir l'[exemple sur MDN]).
Les portées sont une nouvelle fonctionnalité du module [CSS Cascading and
Inheritance Level 6]. Elles sont une manière plus naturelle de définir des
règles :
```css
@scope (.card) {
:scope {
/*...*/
}
article {
/*...*/
header {
/*...*/
}
footer {
/*...*/
button {
/*...*/
}
}
}
}
```
La puissance de @scope vient de plusieurs faits :
* Il suit des règles de proximité : un élément est stylisé avec les règles de
portée les plus proches ;
* Il n'ajoute aucune spécificité au sélecteur, ce qui signifie qu'il peut être
remplacé plus facilement ;
* Il est plus expressif.
[CSS Cascading and Inheritance Level 6]: https://drafts.csswg.org/css-cascade-6/
[exemple sur MDN]: https://developer.mozilla.org/en-US/docs/Web/CSS/@scope#how_scope_conflicts_are_resolved