CSS · Démo interactive

12 fonctionnalités CSS modernes
en action

Chaque section illustre la propriété directement dans le navigateur. Pas de framework, pas de JS superflu — juste du CSS natif qui a bien grandi.

01

:has()

Sélectionne un parent selon son contenu. La carte avec image reçoit automatiquement un style différent — sans JS.

photo

Carte avec image — :has(img) s'applique ✓

Carte sans image — style de base

<div class="card">
  <img src="photo.jpg" alt="photo">
  <p>Carte <strong>avec image</strong></p>
</div>
<div class="card">
  <p>Carte <strong>sans image</strong></p>
</div>
.card {
  padding: 1rem; margin-bottom: 1rem; color: #c9d4e8;
  background: #131a24; border: 1px solid #1e2d44; border-radius: 8px;
}
.card img { width: 100%; border-radius: 6px; margin-bottom: .5rem; }

/* Sélectionne le PARENT selon son contenu — sans JS */
.card:has(img) {
  border-color: #c9952a;
  background: rgba(201, 149, 42, .1);
}
02

@container

Style basé sur la taille du conteneur parent, pas de la fenêtre. Le composant à droite est plus large → disposition en ligne activée.

Conteneur étroit
Titre

Disposition en colonne

Conteneur large → disposition en ligne
Titre

@container actif

<div class="wrap">
  <div class="card">
    <div class="thumb"></div>
    <div>
      <strong>Titre</strong>
      <p>Disposition adaptative</p>
    </div>
  </div>
</div>
.wrap { container-type: inline-size; }   /* devient un conteneur de requête */

.card {
  display: flex; flex-direction: column; gap: .75rem; padding: 1rem;
  background: #131a24; border: 1px solid #1e2d44; border-radius: 8px; color: #c9d4e8;
}
.thumb { width: 56px; height: 56px; border-radius: 6px; background: #c9952a; flex-shrink: 0; }

/* Style selon la largeur du CONTENEUR, pas de la fenêtre */
@container (min-width: 280px) {
  .card { flex-direction: row; align-items: center; }
}
03

text-wrap: balance

Le navigateur équilibre automatiquement les lignes pour éviter les orphelins moches dans les titres.

Sans balance

Un titre un peu trop long qui se coupe de façon inégale et disgracieuse

text-wrap: balance

Un titre un peu trop long qui se coupe de façon inégale et disgracieuse

<h3 class="title">
  Un titre un peu trop long qui se coupe de façon inégale et disgracieuse
</h3>
.title {
  max-width: 22ch;
  color: #e8edf7;
  /* le navigateur équilibre les lignes, sans orphelin moche */
  text-wrap: balance;
}
04

subgrid

Les enfants héritent des tracks de la grille parente. Les boutons s'alignent en bas malgré des titres de longueurs différentes.

Titre court

Description de cet élément.

Titre vraiment beaucoup plus long que les autres ici

Court.

Titre moyen

Les boutons s'alignent grâce au subgrid — magie.

<div class="grid">
  <article class="item">
    <strong>Titre court</strong>
    <p>Description de cet élément.</p>
    <button>Action →</button>
  </article>
  <article class="item">
    <strong>Titre vraiment beaucoup plus long que les autres ici</strong>
    <p>Court.</p>
    <button>Action →</button>
  </article>
</div>
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }

.item {
  display: grid;
  grid-row: span 3;              /* occupe 3 lignes du parent… */
  grid-template-rows: subgrid;   /* …et hérite de leurs tailles */
  gap: .4rem; padding: 1rem; color: #c9d4e8;
  background: #131a24; border: 1px solid #1e2d44; border-radius: 8px;
}
/* Résultat : titres et boutons alignés d'une carte à l'autre */
.item button { justify-self: start; }
05

color-mix()

Mélange deux couleurs en CSS pur, avec contrôle du ratio et de l'espace colorimétrique.

#e84545
+
#4577e8
=
25%
50%
75%
<div class="mix">
  <div class="swatch a">#e84545</div>
  <div class="swatch b">#4577e8</div>
  <div class="swatch r25">25%</div>
  <div class="swatch r50">50%</div>
  <div class="swatch r75">75%</div>
</div>
.mix { display: flex; gap: .5rem; flex-wrap: wrap; }
.swatch {
  width: 80px; height: 80px; border-radius: 8px; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: monospace; font-size: .7rem;
}
.a { background: #e84545; }
.b { background: #4577e8; }

/* Mélange deux couleurs — oklch = dégradé perceptuellement uniforme */
.r25 { background: color-mix(in oklch, #e84545 25%, #4577e8); }
.r50 { background: color-mix(in oklch, #e84545 50%, #4577e8); }
.r75 { background: color-mix(in oklch, #e84545 75%, #4577e8); }
06

CSS Nesting

Imbrication native sans Sass ni PostCSS. La card entière est stylée dans un seul bloc CSS imbriqué.

Titre stylé via nesting natif

Tout est imbriqué dans .nest-card { } — enfants et états inclus.

<div class="card">
  <p class="title">Titre stylé via nesting natif</p>
  <p>Tout est imbriqué dans .card { }</p>
  <button class="btn">Survolez ✶</button>
</div>
.card {
  padding: 1.25rem; color: #c9d4e8;
  background: #0d1117; border: 1px solid #1e2d44; border-radius: 8px;

  /* Imbrication native, sans Sass ni PostCSS */
  & .title { color: #c9952a; font-weight: 700; }

  & .btn {
    margin-top: .75rem; padding: .5rem 1rem; cursor: pointer;
    color: #c9d4e8; background: #1a2333;
    border: 1px solid #1e2d44; border-radius: 6px;

    &:hover { background: #c9952a; color: #0d1117; }
  }
}
07

dvh — Dynamic Viewport Height

100dvh s'adapte aux barres UI mobiles qui apparaissent/disparaissent. Fini le contenu coupé sur Safari iOS.

100vh
⚠ déborde
100dvh
✓ parfait
<section class="hero">
  <h1>Plein écran, sans coupure mobile</h1>
</section>
.hero {
  min-height: 100dvh;   /* dynamic viewport height : suit les barres UI mobiles */
  /* variantes : 100svh (small) · 100lvh (large) */
  display: grid; place-items: center;
  color: #e8edf7; background: #131a24;
}
08

aspect-ratio

Ratio fixe sans hack padding. Redimensionnez la fenêtre — les boîtes maintiennent leur proportion exacte.

1 / 1
16 / 9
4 / 3
3 / 4
<div class="grid">
  <div class="box r-1x1">1 / 1</div>
  <div class="box r-16x9">16 / 9</div>
  <div class="box r-4x3">4 / 3</div>
</div>
.grid { display: flex; gap: 1rem; flex-wrap: wrap; }
.box {
  width: 140px; display: grid; place-items: center; color: #0d1117;
  background: #c9952a; border-radius: 8px; font-family: monospace; font-size: .8rem;
}
/* Ratio fixe, sans le hack padding-top: 56.25% */
.r-1x1  { aspect-ratio: 1; }
.r-16x9 { aspect-ratio: 16 / 9; }
.r-4x3  { aspect-ratio: 4 / 3; }
09

scroll-timeline

Animations CSS liées à la progression du scroll. La barre en haut de page et ci-dessous suivent votre scroll — zéro JS.

↑ barre liée au scroll de la page via animation-timeline

<div class="track"><div class="bar"></div></div>
@keyframes progress { from { width: 0 } to { width: 100% } }

.track { height: 6px; background: #1e2d44; border-radius: 99px; }
.bar {
  height: 100%; background: #c9952a; border-radius: 99px;
  /* animation pilotée par le scroll de la page — zéro JS */
  animation: progress linear both;
  animation-timeline: scroll(root block);
}
10

:is()

Regroupe plusieurs sélecteurs en un. Un seul bloc de règles pour button, a et span — et ça gère la spécificité intelligemment.

Lien Pastille em — non ciblé
<div class="demo">
  <button class="btn">Bouton</button>
  <a href="#" class="link">Lien</a>
  <span class="chip">Pastille</span>
  <em class="other">em — non ciblé</em>
</div>
.demo { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }

/* Un seul bloc de règles pour plusieurs sélecteurs */
:is(.btn, .link, .chip) {
  padding: .35rem .8rem; border-radius: 6px;
  color: #c9952a; border: 1px solid currentColor;
  background: none; text-decoration: none;
}
11

accent-color

Harmonise les contrôles natifs (checkbox, radio, range…) avec la palette OWL en une ligne, sans CSS custom complexe.

<label><input type="checkbox" checked> Case cochée</label>
<label><input type="radio" name="r" checked> Radio</label>
<label>Curseur <input type="range" value="65"></label>
/* Harmonise checkbox, radio, range… avec la palette, en une ligne */
:root { accent-color: #c9952a; }

label { display: block; margin: .5rem 0; color: #c9d4e8; }
12

anchor()

Positionne un élément relativement à une ancre CSS, où qu'elle soit dans le DOM. Tooltips sans JS de positionnement. (Chrome 125+)

👋 Ancré via CSS anchor() — sans JS

Compatibilité : Chrome 125+ · Alternative incluse pour les autres navigateurs

<button class="trigger">Survolez pour l'info-bulle ❆</button>
<div class="tooltip">👋 Ancré via CSS anchor() — sans JS</div>
.trigger {
  anchor-name: --btn;              /* l'élément devient une ancre */
  padding: .5rem 1rem; color: #c9d4e8;
  background: #1a2333; border: 1px solid #1e2d44; border-radius: 6px;
}
.tooltip {
  position: fixed;
  position-anchor: --btn;          /* positionné par rapport à l'ancre */
  top: anchor(bottom); left: anchor(center);
  margin-top: .5rem; padding: .4rem .7rem; border-radius: 6px;
  background: #c9952a; color: #0d1117; font-size: .85rem;
}
/* Chrome 125+ — prévoir une alternative pour les autres navigateurs */