Les dernières ressources publiées sur Alsacreations.com
Il est loin le temps où Visual Studio Code courait derrière Cursor ou Claude Code. Aujourd'hui, l'éditeur open source de Microsoft est l'un des environnements de développement qui intègre le mieux l'intelligence artificielle. [...]
Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com
La gestion de la portée des styles est un défi historique en CSS. Des méthodologies comme BEM aux solutions techniques comme le Shadow DOM ou les CSS Modules, nous avons toujours cherché à éviter que le style d'un composant ne se propage sur ses voisins. L'arrivée de la règle @scope change la donne en offrant une solution native, flexible et puissante.
@scopeLa règle @scope permet de cibler des éléments dans un fragment spécifique du DOM.
Dans cet exemple simple, seuls les <h2> à l'intérieur des éléments portant la classe .card seront colorés en hotpink. Les styles n'affecteront pas les autres <h2> de la page :
@scope (.card) {
/* Cible les h2 uniquement dans .card */
h2 {
color: hotpink;
}
}
Dans cet autre exemple sont ciblés uniquement les <h2> à l'intérieur des éléments portant la classe .card contenant un élément .title :
@scope (.card:has(.title)) {
/* Cible les h2 uniquement dans .card qui contient .title */
h2 {
color: hotpink;
}
}
Voici le tableau de compatibilité de @scope, que vous pouvez également consulter sur Caniuse.com :
:scope et l'esperluette &À l'intérieur d'un bloc @scope, le navigateur a besoin d'un moyen de désigner l'élément racine lui-même (celui qui porte la classe ou l'attribut de départ). Il existe deux façons de procéder :
:scope : Une pseudo-classe qui référence logiquement l'élément racine. C'est le cœur technique du dispositif.& (Esperluette) : Comme dans le nesting classique, elle permet de coller des états à la racine (ex: &:hover).Dans la plupart des cas, l'usage du & pour les états interactifs est recommandé pour sa simplicité.
Par exemple, si on souhaite que son composant .card ait un fond gris, mais que ses éléments internes aient leurs propres styles, on va se servir de :scope :
@scope (.card) {
/* Cible l'élément .card lui-même */
:scope {
background: var(--surface);
border: 1px solid var(--border-light);
}
}
Pour les éléments hautement réutilisables comme les boutons, :scope associé à & (esperluette) permettent de centraliser la logique interactive (hover, focus, active) en utilisant les variables CSS.
@scope (.btn) {
:scope {
/* Variables par défaut */
--button-background-color: var(--form-background, Field);
--button-text-color: var(--on-form, ButtonText);
background-color: var(--button-background-color);
color: var(--button-text-color);
transition: all var(--transition-duration);
}
/* Logique interactive unique pour toutes les variantes */
&:hover, &:focus-visible {
background-color: oklch(from var(--button-background-color) calc(l * 0.9) c h);
}
&:disabled {
opacity: 0.8;
cursor: not-allowed;
}
/* Variantes : On ne change que les valeurs, pas la logique */
&.btn-primary {
--button-background-color: var(--primary);
--button-text-color: var(--on-primary);
}
}
.card .title ou .card > .title ?C'est bien vrai ça, on pourrait croire que @scope ne sert qu'à nous compliquer la vie. Alors que… pas du tout !
@scope, c'est la racine la plus proche de l'élément dans le DOM qui l'emporte. C'est idéal pour les thèmes imbriqués..card .title a une spécificité de (0,2,0). À l'intérieur d'un @scope, le sélecteur .title reste à (0,1,0), facilitant les surcharges sans utiliser !important.>) est fragile. Si on ajoute une div intermédiaire pour du layout, le style casse. @scope tolère n'importe quelle profondeur tant que la limite n'est pas franchie.En outre, grâce à la priorité par proximité, @scope permet d'imbriquer des thèmes sans conflit, peu importe l'ordre des CSS.
@scope (.theme-dark) {
:scope { background: #1a1a1a; color: white; }
.title { color: var(--color-pink-300); }
}
@scope (.theme-light) {
:scope { background: white; color: #1a1a1a; }
.title { color: var(--color-blue-600); }
}
Contrairement aux sélecteurs descendants classiques, @scope introduit deux concepts majeurs :
Dans l'exemple suivant, le navigateur cherche .card pour y colorer les h2 en rose. Cependant, dès qu'il rencontre un élément .content, il cesse d'appliquer les styles à ses descendants :
@scope (.card) to (.content) {
/* Cible les .title dans .card mais s'arrête à .content */
.title {
color: hotpink;
}
}
C'est l'atout majeur de @scope : la capacité de définir un périmètre d'exclusion via la clause to. Cela permet de protéger des zones de contenu riche (WYSIWYG, slots) ou des composants imbriqués qui ne doivent pas être affectés par les styles du parent.
BEM est une convention efficace mais verbeuse, et elle repose entièrement sur votre discipline de nommage et nécessite des noms de classes uniques (.card__title, .hero__title--alternate).
Avec @scope, vous pouvez garder des noms plus simples et réutilisables, tout en garantissant que les styles restent confinés à leur contexte : le HTML est épuré des préfixes répétitifs et grâce au to (.content), un titre de card situé dans un élément .content sera jamais affecté par le style de la carte, car il est "hors limite".
L'adoption de @scope marque une étape vers la fin de l'ère "tout BEM" au profit d'une approche plus sémantique et structurelle.
Dans un projet concret, BEM, @layer et @scope peuvent cependant coexister :
On ne va pas se mentir : l'intégration de @scope dans un projet utilisant déjà un framework utilitaire comme Tailwind peut sembler redondante au premier abord.
Il existe cependant quelques cas d'usage où @scope peut apporter une valeur ajoutée significative, notamment pour les composants complexes et les zones de contenu dynamique qui ne sont pas facilement gérables avec les classes utilitaires seules.
Par exemple, dans Tailwind, styler du HTML brut (venant d'un CMS) nécessite souvent l'utilisation du plugin @tailwindcss/typography (classe .prose). Cependant, ce plugin est parfois trop rigide. Avec @scope, on peut créer des "bulles" de styles spécifiques pour son contenu sans polluer le reste du site et sans créer de classes complexes :
@layer components {
@scope (.prose-custom) to (.content) {
/* On style les balises nues uniquement dans ce contexte */
h2 { @apply text-2xl font-bold mb-4 text-primary; }
p { @apply leading-relaxed mb-6; }
/* Les limites protègent les composants Tailwind imbriqués */
}
}
Si l'on devait résumer les avantages de @scope en quelques points clés :
.card-header__title. On utilise des classes simples (.title, .media, .header, .content) et faciles à retenir.to pour définir une limite haute et basse et garantir que les utilitaires appliqués à un composant enfant ne seront jamais écrasés par les styles du parent.@scope apparaît clairement, permettant de voir immédiatement quelle racine applique le style, plutôt que de chercher l'origine dans une pile de sélecteurs descendants complexes..card .btn) qui augmentent la spécificité, @scope maintient une spécificité faible. Cela permet à ses classes utilitaires (ex: p-4) de rester prioritaires et faciles à surcharger si besoin.Si l'on ne devait en retenir qu'un seul, il s'agirait sans aucun doute de Conflits de nommage réduits. En effet, des noms de classes génériques comme .title peuvent être utilisées dans plusieurs scopes sans risque de conflit, car elles ne s'appliquent qu'à leur propre scope. Et tout le monde sait que nommer les choses c'est… compliqué.
@scope ne remplace pas les outils actuels, il complète avantageusement l'outillage natif déjà mis en place par CSS (cascade, @layer, :where(), etc.).
Totalement inadapté pour les styles globaux (reset, typographie de base, utilitaires) qui doivent se propager partout, il devient en revanche un allié précieux pour les styles de composants et les zones de contenu dynamique.
Utilisons-le dans notre @layer(components) pour isoler nos "organismes" (cartes, headers, modales). C'est la fin du "CSS qui bave" et le début d'une architecture plus proche de la structure réelle de vos pages.
Note : Le support navigateur est désormais excellent dans les versions récentes de Firefox, Chrome, Edge et Safari. Pour les projets nécessitant un support legacy, une stratégie de Progressive Enhancement via @supports (scope: html) est préconisée.
L'événement scrollend est une nouvelle API standardisée pour détecter la fin du défilement dans une page web.
Durant des années nous avons du jongler avec moult événements, astuces JavaScript et calculs pour attraper l'action de scroll, notamment le combiner à un debounce : cette approche consistait à déclencher un timer à chaque événement de défilement et à considérer que le scroll était terminé si aucun nouvel événement ne survenait pendant un certain délai, typiquement 100 à 200 millisecondes, selon la lourdeur de la page et la réactivité attendue.
Les inconvénients étaient des faux positifs lors de pauses qui n'en étaient pas vraiment, la consommation de ressources (processeur, carte graphique), et le délai choisi restait approximatif. De plus, on ne prenait pas en compte les animations de défilement fluide ou les gestes tactiles avec inertie.
// Approche classique/ancienne avec debounce â ï¸
let scrollTimer = null;
window.addEventListener('scroll', function() {
// Annuler le timer précédent si le scroll continue
if (scrollTimer !== null) {
clearTimeout(scrollTimer);
}
// Définir un nouveau timer
scrollTimer = setTimeout(function() {
// Code exécuté "à la fin" du scroll
console.log('Scroll terminé (probablement)');
// Exemple : charger plus de contenu
loadMoreContent();
// Exemple : enregistrer la position de lecture
trackScrollDepth();
}, 150); // Délai arbitraire de 150ms
}, false);
Désormais scrollend (disponible dans tous les navigateurs) est aussi simple à utiliser que n'importe quel événement natif du navigateur. Il suffit d'ajouter un écouteur sur l'élément défilable concerné, que ce soit la fenêtre principale ou un conteneur spécifique.
// Avec scrollend â
window.addEventListener('scrollend', function() {
// Code exécuté réellement à la fin du scroll
console.log('Scroll terminé');
// Exemple : charger plus de contenu
loadMoreContent();
// Exemple : enregistrer la position de lecture
trackScrollDepth();
});
L'événement se déclenche de manière fiable dans tous les scénarios de défilement : que l'utilisateur utilise la molette de la souris, fasse glisser la barre de défilement, utilise les touches du clavier, effectue un geste tactile sur mobile/tablette, ou même lorsqu'un défilement programmé via scrollTo() ou scrollIntoView() se termine.
Pour le lazy-loading cet événement permet de déclencher le chargement de nouvelles données uniquement lorsque l'utilisateur a réellement terminé son défilement, y compris en infinite scroll, évitant ainsi des requêtes prématurées. On peut aussi parfaitement l'utiliser pour afficher des indicateurs de position de lecture qui se mettent à jour de façon plus pertinente, même si on peut le faire de manière encore plus élégante en CSS : Les Animations liées au scroll avec CSS.
Enfin pour les adeptes du tracking, scrollend permet de savoir jusqu'où les internautes défilent réellement dans une page, plutôt que de se baser sur des approximations.
D'après Can I Use : scrollend on est plutôt à l'aise car tous les navigateurs majeurs le supportent (Chrome, Edge, Firefox, Safari). Nul besoin de polyfill.
Pendant plus d'une décennie, nous avons traité le Web comme une collection d'affiches statiques imprimées sur du papier de verre : une pour mobile, une pour tablette, une pour desktop. Mais le Web n'est pas une série de tailles d'écrans fixes. C'est un fluide continu.
Bienvenue dans l'ère de l'Intrinsic Web Design (ou Design Intrinsèque), un terme introduit Jen Simmons en 2018 où le contenu dicte la mise en page, et non l'inverse. L'objectif est de créer des composants qui s'adaptent à leur contexte, qu'ils soient dans une sidebar réduite ou dans un header très large.
Le CSS moderne nous offre un vocabulaire de précision pour définir comment les boîtes doivent se comporter face à leur contenu et leur conteneur :
auto : L'ancien roi. Contextuel, imprévisible parfois, il laisse le navigateur calculer la taille selon le modèle de boîte standard (display).min-content : "Je veux être aussi petit que possible." Le navigateur va essayer de réduire la boîte jusqu'à ce que le mot le plus long ou l'élément le plus large force la largeur minimale. C'est le "soft wrapping" ultime.max-content : "Je prends toute la place dont j'ai besoin." La boîte s'élargit pour contenir tout le texte sans aucun retour à la ligne, quitte à provoquer un scroll horizontal (à utiliser avec prudence !).fit-content : Le compromis parfait. C'est mathématiquement équivalent à min(max-content, available-space). La boîte s'adapte au contenu, mais s'arrête poliment si elle touche le bord du conteneur parent.stretch : L'élément s'étire pour remplir l'axe disponible. C'est souvent le comportement par défaut des flex-items ou des grid-items.clamp(), min() et max()Fini le temps où l'on devait déclarer font-size: 16px puis le changer à 18px au breakpoint tablette, et 20px sur desktop.
Avec les fonctions mathématiques CSS, la fluidité est native. La fonction clamp() est sans doute la plus polyvalente d'entre elles. Elle permet de définir une valeur minimale, une valeur idéale (fluide) et une valeur maximale.
h1 {
/* Taille min: 2rem, Idéale: 5% du viewport, Max: 4rem */
font-size: clamp(2rem, 5vw + 1rem, 4rem);
}
Contrairement à une approche type Tailwind (qui demande souvent d'empiler des préfixes text-m md:text-l lg:text-xl), clamp() gère l'interpolation mathématique tout seul. Plus de breakpoints figés, plus d'effets de saut lors du redimensionnement de la fenêtre.
ð§ L'outil Alsacréations : Pour générer ces formules sans avoir un doctorat en mathématiques, notre outil maison Elastic est toujours là pour vous servir.

Vous utilisez déjà Flexbox et Grid, mais exploitez-vous vraiment leurs capacités intrinsèques ? Ces modules ont été conçus pour gérer l'espace restant et l'espace manquant sans Media Queries.
Flexbox brille sur un axe. Ses propriétés de base sont des moteurs de fluidité :
flex-wrap: wrap : La base du responsive. Si ça ne rentre pas, ça passe à la ligne.flex-grow: 1 : "Prends tout l'espace vide disponible".flex-shrink: 1 : "Si on manque de place, réduis-toi".
Grid introduit l'unité fr (fraction), qui distribue l'espace libre après le calcul des tailles fixes.
Mais la véritable puissance réside dans minmax(). Cette fonction permet de générer des colonnes occupant une largeur minimale (300px) tout en s'étirant pour remplir l'espace disponible : grid-template-columns: minmax(300px, 1fr)
Le nom d'usage "RAM" (pour "Repeat Auto Minmax") est usuellement donné au snippet magique qui génère une grille responsive automatique sans aucun breakpoint :
.grid-auto {
display: grid;
/* 1. repeat: Répète les colonnes...
2. auto-fit: ...autant de fois que possible dans le conteneur...
3. minmax: ...avec une taille min de 250px et max de 1fr.
*/
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
Résultat : Sur desktop, vous avez 4 colonnes. Sur tablette, 2 ou 3. Sur mobile, 1 seule. Nul besoin de @media ni de breakpoints pour cela.
Plutôt que de repartir d'une page blanche et de se battre avec les calculs de fr ou de minmax, le projet d'Alsacréations nommé Bretzel propose des styles utilitaires neutres et réutilisables qui exploitent ces concepts de design intrinsèque.
Contrairement aux frameworks lourds, Bretzel offre des solutions légères qui s'adaptent sans Media Queries pour bon nombre d'entre elles. On y retrouve notamment :
repeat(auto-fill, minmax(...)) pour des listes de cartes qui se réorganisent d'elles-mêmes.Ces patterns sont "incassables" car ils ne dépendent pas d'une largeur d'écran arbitraire mais des limites physiques imposées à leurs conteneurs.

C'est ici que tout bascule : tandis que les Media Queries interrogent l'écran (le Viewport), les Container Queries interrogent un ancêtre ou le parent direct de l'élément.
Cela permet de créer des composants réellement agnostiques. Une "Card" placée dans une sidebar étroite s'affichera en mode "compact", et la même "Card" dans le contenu principal s'affichera en mode "étendu".
Mise en pratique :
/* 1. On définit le conteneur */
.card-wrapper {
container-type: inline-size; /* Surveille la largeur */
container-name: card; /* Optionnel, pour cibler spécifiquement */
}
/* 2. Styles de base du composant */
.card {
display: flex;
flex-direction: column; /* Mobile-first (ou plutôt Small-container-first) */
gap: 1rem;
}
.card-image {
aspect-ratio: 16/9;
object-fit: cover;
}
/* 3. Les styles s'adaptent si le CONTENEUR > 500px */
@container card (width > 500px) {
.card {
flex-direction: row; /* On passe à l'horizontale */
align-items: center;
}
.card-image {
width: 30cqi; /* 30% de la largeur du CONTENEUR (Container Query Inline unit) */
max-width: 200px;
}
.card-content {
flex: 1;
font-size: clamp(1rem, 3cqi + 1rem, 2rem); /* Typo fluide basée sur le conteneur */
}
}
Notez l'unité cqi : Comme vw mais relative au conteneur. Indispensable pour une typographie contextuelle parfaite.
Et pour les plus audacieux, l'association avec :has() permet de transformer automatiquement un parent en conteneur si un certain enfant est présent :
/* Si l'élément .box contient un composant .card, il devient un conteneur */
.box:has(> .card) {
container-type: inline-size;
}
Ou encore plus fort :
/* Le parent de .card, quel qu'il soit devient un conteneur */
.card {
*:has(> &) {
container-type: inline-size;
}
}
ð Pour en savoir plus sur ce sujet, n'hésitez pas à consulter notre article détaillé "Les Container Queries en CSS"
Aujourd'hui, @container interroge la taille. Demain, avec les Style Queries, nous interrogerons la valeur d'une propriété ou d'une variable CSS.
Imaginez un composant qui change d'aspect non pas selon sa taille, mais selon son "thème" défini par une variable parente :
/* Conditionner le style selon une variable CSS */
@container style(--variant: highlighted) {
.card {
background: gold;
border: 5px solid black;
animation: shake 0.5s;
}
}
Cela ouvre la porte à une logique conditionnelle directement en CSS, séparant totalement la présentation de la structure HTML.
Si vous pensiez qu'on avait atteint le sommet, détrompez-vous. Voici ce qui arrive (et qui est déjà testable, notamment sous Firefox derrière des flags) :
À ce jour, les variables CSS ne fonctionnent pas dans les Media Queries, il est donc nécessaire de répéter systématiquement les mêmes requêtes @media (width > 48rem) à chaque fois que nécessaire.
Bientôt, vous pourrez scripter vos environnements :
/* Définition (bientôt natif) */
@custom-media --tablet (max-width: 48rem);
@custom-media --dark-mode (prefers-color-scheme: dark);
/* Utilisation */
@media (--tablet) {
/* ... */
}
Le Responsive ne concerne pas que la taille. Il concerne aussi les préférences utilisateur. La fonction light-dark() simplifie drastiquement la gestion du Dark Mode, sans avoir à envelopper tout votre CSS dans des blocs @media (prefers-color-scheme: dark).
:root {
/* Le navigateur choisit la bonne couleur automatiquement */
color-scheme: light dark;
--surface: light-dark(var(--color-white), var(--color-gray-900));
--on-surface: light-dark(var(--color-gray-900), var(--color-gray-100));
}
body {
background-color: var(--surface);
color: var(--on-surface);
}
En 2026, faire du responsive ne consiste plus à boucher les trous entre deux breakpoints. C’est accepter qu’on n'est pas là pour faire de l'impression papier, mais pour construire des systèmes qui s'adaptent à de multiples contraintes.
En adoptant l’Intrinsic Web Design, on redonne au navigateur son rôle d’outil intelligent capable d'interpréter nos intentions plutôt que d'exécuter des ordres rigides. Moins de Media Queries, c’est plus de robustesse, moins de dette technique et, surtout, un Web qui respire enfin et qui résistera au prochain device pliable en forme de triangle.
L'API Web Share est un agrément de l'expérience utilisateur permettant de s'affranchir des dizaines de scripts tiers (parfois lourds et intrusifs) à ajouter à une page pour chaque réseau social.
C'est très facile à mettre en place et à cerner, on peut le constater en survolant la spécification W3C Web Share API qui ne pèse pas plusieurs dizaines de pages.
Historiquement, pour permettre à un utilisateur de partager un article, nous devions intégrer des boutons spécifiques pour chaque plateforme (Twitter, Facebook, LinkedIn, etc.). Cela impliquait souvent le chargement de bibliothèques JavaScript externes, réseau par réseau, ou des solutions groupées telles que addThis, allouridssant la page, posant des problèmes de performance et de respect de la vie privée.
L'API Web Share change la donne en permettant aux sites web d'utiliser le mécanisme de partage natif du système d'exploitation. C'est donc la même popup (ou popin) qui s'ouvre lorsque vous partagez une photo depuis votre galerie.

Le bénéfice est triple :
(On rappelle qu'il vaut mieux utiliser un bouton pour une action en restant sur la même page, et un lien pour une action provoquant un changement de contexte/page).
Bien que l'API soit standardisée, le comportement visuel varie selon la plateforme :
| Caractéristique | Mobile (iOS / Android) | Desktop (Windows / macOS) |
|---|---|---|
| Interface | Popup de partage native | Menu contextuel du système ou fenêtre dédiée |
| Intérêt | Usage naturel | Pratique mais moins évident (souvent limité à l'email ou AirDrop) |
| Support | Quasi universel | Bon sur Chromium, Safari, Edge ; pas Firefox |
ð L'API Web Share ne fonctionne que dans un contexte sécurisé, c'est-à-dire sur HTTPS, et doit impérativement être déclenchée par une action volontaire de l'utilisateur (un clic ou un appui) sinon ce sera bloqué.

Si le support s'est largement généralisé, surtout sur mobile, certains environnements comme Firefox sur certains OS desktop ne l'ont pas encore appliqué alors même que la version mobile de Firefox le reconnaît.
On va donc penser amélioration progressive :
navigator.share existe.mailto:, liens directs vers les réseaux, etc).D'un point de vue technique, l'objet passé à la méthode navigator.share() est flexible mais chaque clé répond à des contraintes spécifiques définies par la spécification W3C.
titleIl s'agit d'une chaîne de caractères représentant le titre du document ou de l'élément partagé. On va le retrouver comme sujet de l'e-mail si l'utilisateur choisit une application de messagerie ou comme titre de l'aperçu. Ce n'est pas du HTML, uniquement du texte brut.
textC'est le corps descriptif du partage, en général on y place un résumé ou un message personnalisé. Dans le cas d'un partage via SMS ou WhatsApp, c'est ce contenu qui remplira le champ de saisie.
urlUne chaîne de caractères représentant une URL valide (absolue ou relative). Le navigateur résout automatiquement les URL relatives par rapport à l'URL de la page courante avant de les envoyer au système.
filesC'est l'ajout le plus complexe. : un tableau d'objets File (généralement créés via un constructeur new File() ou récupérés depuis un <input type="file">). Avant de partager des fichiers, il est impératif d'utiliser la méthode navigator.canShare({ files: mesFichiers }), elle va renvoyer un booléen indiquant si le système accepte le format et le poids des fichiers. En général on accepte couramment les images, les pdf, audio et vidéo mais pas les exécutables (.exe, .js) qui seront bloqués
Voici un exemple de code utilisant les promesses.
const shareBtn = document.querySelector('#btn-partage');
// On ne montre le bouton que si l'API est supportée
if (navigator.share) {
shareBtn.style.display = 'block'; // Ou un équivalent
shareBtn.addEventListener('click', async () => {
try {
await navigator.share({
title: 'Le titre de l\'article',
text: 'Une courte description',
url: window.location.href
});
console.log('Contenu partagé avec succès !');
} catch (err) {
// L'utilisateur a annulé ou le partage a échoué
console.log(`Erreur ou annulation : ${err}`);
}
});
} else {
// Optionnel : afficher une autre solution (ex: copier le lien)
console.log("Web Share API non supportée sur ce navigateur.");
}
Attention désormais dans les frames (<iframe> en réalité, ce qui est le cas de la démo ci-dessus hébergée par codepen) vous devez autoriser spécifiquement cette API pour des raisons de sécurité avec l'attribut allow="web-share".