Les resets CSS standalone connus (Normalize, Meyer, sanitize.css) datent d'une époque où les UA stylesheets divergeaient franchement entre navigateurs. En 2024, la convergence des moteurs réduit cette dette, mais la vraie question n'est plus "neutraliser les UA" : c'est "offrir une base sans opinion projet, prévisible, maintenable, et résistante aux effets de cascade involontaires". Ce fichier répond à cette question avec les outils CSS natifs actuels : @layer, nesting natif CSS, et une stratégie de spécificité assumée.
Sans une ligne de SCSS.
Pourquoi pas Normalize ou Meyer ?
Tout d'abord, état des lieux
| Reset | Dernière version | Statut |
|---|---|---|
| Meyer Reset | v2.0 (janvier 2011) | abandonné |
| Normalize.css (necolas) | v8.0.1 (2018) | abandonné |
| sanitize.css (csstools) | v13.0.0 (2021) | maintenu |
| Tailwind Preflight | v4.0 (janvier 2025) | maintenu |
Mais sans Tailwind sur le projet, pas de Tailwind Preflight…
Meyer Reset (2011) passe tout à zéro sans discrimination. C'est radical — façon table rase existentielle — mais ça oblige à redéfinir des comportements natifs utiles (curseur sur abbr[title], styles de sub/sup, etc.) et ne dit rien sur la cascade.
Normalize.css adopte le parti inverse : conserver les comportements UA cohérents et ne corriger que les incohérences. C'est pragmatique, mais les règles sont hors layer, sans stratégie de spécificité explicite, et les commentaires sont limités — difficile à auditer ou adapter.
Ce reset prend un troisième chemin : neutralisation ciblée des comportements réellement problématiques, encapsulation dans @layer reset pour que toute règle projet l'emporte sans combat de spécificité, et nesting natif sous :root qui ajoute (0,1,0) à chaque sélecteur — une légère élévation de spécificité voulue, documentée, qui limite les écrasements accidentels par des sélecteurs projet trop permissifs.
Structure du fichier
@layer reset { … }
L'encapsulation dans un layer nommé est la première décision structurante. Les règles hors layer (couche projet) l'emportent toujours sur les règles en layer, quelle que soit la spécificité. Pas besoin de !important pour surcharger — il suffit d'écrire la règle projet hors layer.
L'ordre des layers doit être déclaré explicitement au point d'entrée :
@layer reset, base, components, utilities;
Sélecteurs globaux
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
background-repeat: no-repeat;
}
Ces sélecteurs restent hors :root : le nesting CSS ne permet pas de les imbriquer sans altérer leur portée. background-repeat: no-repeat est souvent absent des resets courants — il neutralise la répétition par défaut sur ::before/::after lors de l'usage de background-image ou mask-image.
Gestion tactile
@media (any-hover: none) {
html { -webkit-tap-highlight-color: transparent; }
a[href], button:not([disabled]), [role="button"], … {
touch-action: manipulation;
}
}
touch-action: manipulation supprime le délai de 300 ms au tap sur les éléments interactifs, sans désactiver le pinch-zoom sur le document. La media query any-hover: none cible les appareils sans pointeur précis, évitant d'appliquer ces règles à des contextes desktop où elles seraient neutres mais inutiles.
Formulaires
neutralisation totale assumée
button, input, select, textarea, optgroup, option {
font: inherit;
color: inherit;
letter-spacing: inherit;
word-spacing: inherit;
border: none;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
Choix délibérément strict : un élément de formulaire non stylé au niveau projet sera invisible ou inutilisable. C'est voulu — un oubli doit apparaître en review, pas passer inaperçu derrière des styles UA incohérents entre navigateurs. Le <select> a l'air moche ? Bien. Il attend qu'on s'en occupe.
La responsabilité du projet : définir, si nécessaire, dans les layers base/components les états visuels (hover, focus, active, disabled, invalid), les affordances (cursor, outline), et les styles focus-visible pour l'accessibilité clavier.
Masquage accessible
À ce stade, .sr-only est probablement la classe CSS la plus recopiée de l'histoire du web — Bootstrap, Tailwind, WHCM, chaque framework a sa variante, souvent légèrement différente, parfois cassée. Autant l'avoir dans son reset, propre et documentée.
.sr-only {
position: absolute;
width: 1px !important;
height: 1px !important;
line-height: 0;
z-index: -10;
border: 0;
white-space: nowrap;
overflow: hidden;
clip: rect(0, 0, 0, 0);
color: inherit;
padding: 0 !important;
margin: 0 !important;
& * { all: inherit !important; }
}
Plusieurs points méritent attention. width/height à 1px (pas 0) : une valeur nulle supprime l'élément du flux AT sur certains lecteurs d'écran. clip est déprécié mais conservé pour les AT anciens qui ignorent clip-path. & * { all: inherit !important } force les enfants à hériter du masquage — un enfant avec display ou visibility explicite ne doit pas casser le masquage parent.
Point de vigilance : si un ancêtre crée un stacking context avec z-index positif, le z-index: -10 peut rendre le focus clavier inatteignable. À surveiller au niveau projet.
Correctifs typographiques et cross-browser
Quelques règles défensives ciblées :
font-family: monospace, monospacesurpre,code,samp,kbd— le doublon force les navigateurs à respecter la taille de fonte héritée sur les éléments monospace imbriqués.h1dans sections imbriquées — les UA stylesheets réduisent encore la taille duh1danssection/article/aside/nav, héritage de HTML5 Outline abandonné mais conservé dans les moteurs comme un fantôme spec qui refuse de partir. La règle explicite rétablit la valeur attendue.fieldset { min-width: 0; min-inline-size: unset }— correctif Firefox (bug 504622) : lemin-widthnatif du fieldset casse les layouts flex/grid.dialog::backdrop { background: none }— le backdrop est volontairement non stylé ; c'est une opinion projet, pas un reset.
Téléchargement
Le fichier est prêt à l'emploi, sans dépendance. À placer en premier dans l'ordre de cascade, avant la déclaration des layers projet.
Taille brute : 14,06 kilo-octets (~5,25 kilo-octets gzip)
/**
* Source Kortic https://www.kortic.com/le-blog-kortic/2026/03/08/reset-css-autonome-universel-et-modernise.html
* licence Creative Commons CC BY-NC-SA 4.0
* https://creativecommons.org/licenses/by-nc-sa/4.0/deed.fr
* Attribution, partage dans les mêmes conditions, pas d'utilisation commerciale
*/
/* =============================================================================
RESET UNIVERSEL
Règles sans opinion projet, applicables à tout contexte.
Structure :
- Les sélecteurs globaux (*, *::before, *::after) restent hors :root —
le nesting CSS ne permet pas de les imbriquer sans altérer leur portée.
- Tout le reste est imbriqué sous :root via le nesting natif (baseline 2023+).
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting
Spécificité :
- Le nesting sous :root ajoute (0,1,0) à chaque sélecteur imbriqué.
Ex : ul nu = (0,0,1), :root ul = (0,1,1).
- C'est un choix assumé : la spécificité légèrement augmentée du reset
réduit les risques d'écrasement accidentel par des règles projet peu spécifiques.
- En contrepartie, surcharger ces règles au niveau projet nécessite
au minimum un sélecteur de même poids — à garder en tête.
https://www.w3.org/TR/selectors/#specificity-rules
Cascade layers :
- Le fichier est encapsulé dans @layer reset.
Les règles hors layer (projet) l'emportent toujours sur les règles en layer,
quelle que soit leur spécificité — ce qui simplifie les surcharges projet.
- L'ordre des layers doit être déclaré explicitement au point d'entrée CSS :
@layer reset, base, components, utilities;
https://developer.mozilla.org/en-US/docs/Web/CSS/@layer
Support :
- Nesting natif + @layer : Chrome/Edge 112+, Firefox 117+, Safari 16.4+
- Compatible SCSS sans modification (Sass 1.65+, août 2023).
============================================================================= */
@layer reset {
/* Sélecteurs globaux — isolés hors :root (non nestables sans effet de portée) */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
/* Neutralise la répétition par défaut des background et mask (y compris sur ::before/::after).
Évite les artefacts visuels non désirés lors de l'utilisation de background-image ou mask-image.
https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat */
background-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
:root {
color-scheme: dark light;
background-color: Canvas;
color: CanvasText; /* Symétrique à background-color: Canvas.
Depuis CSS Color 4, l'initial value de `color` est déjà CanvasText,
donc souvent redondant sur les moteurs à jour, mais l'expliciter
documente l'intention et protège contre un moteur qui hérite encore
d'un color: black implicite (cas résiduel pré-adoption de la spec).
https://developer.mozilla.org/en-US/docs/Web/CSS/system-color */
/* Contrôle du type de défilement de page */
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
/* Si gestion d'option animation */
&.no-motion {
scroll-behavior: auto;
}
}
@media (prefers-reduced-motion: reduce) {
scroll-behavior: auto;
}
/* print-color-adjust.
Empêche le navigateur de supprimer couleurs et images de fond à l'impression.
economy par défaut. Passer à exact si on veut imprimer les arrière-plans, mais pas très écolo…
https://developer.mozilla.org/en-US/docs/Web/CSS/print-color-adjust */
@media print {
* {
print-color-adjust: economy;
}
}
/* Suppression du highlight tactile sur les appareils sans hover.
touch-action: manipulation supprime le délai de 300ms au tap.
https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action
https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover */
@media (any-hover: none) {
html {
-webkit-tap-highlight-color: transparent;
}
a[href],
area[href],
input:not([disabled]):not([type="hidden"]),
summary,
select:not([disabled]),
textarea:not([disabled]),
[role="button"],
button:not([disabled]),
[tabindex="0"],
[contenteditable] {
touch-action: manipulation;
}
}
/* Les UA stylesheets conservent puces et indentation, on neutralise */
& :is(ul, ol, menu) {
list-style: none;
}
/* Neutralisation explicite du ::marker sur les li
(notamment dans menu où list-style: none peut ne pas suffire selon le contexte) */
& li::marker {
content: none;
}
/* picture et img sont inline par défaut, ce qui crée un espace résiduel sous l'image
dû à l'alignement sur la baseline du texte (place réservée aux jambages).
display: block sort l'élément du flux inline et supprime cet espace à la source. */
& picture {
display: block;
}
/* Alignement de base cohérent cross-browser sur les médias inline */
& :is(audio, canvas, progress, video, del, ins) {
display: inline-block;
}
/* Neutralisation totale des éléments de formulaire.
Tous les éléments de formulaire sont ramenés à un état neutre (pas de border,
padding, background, apparence UA). Les éléments non stylés au niveau projet
seront invisibles ou non utilisables, c'est voulu. Un élément oublié doit
apparaître en review, pas passer inaperçu avec des styles UA inconsistants.
Responsabilité projet : définir dans les layers base/components les états
visuels (hover, focus, active, disabled, invalid), affordances (cursor, outline),
et accessibilité clavier (focus-visible).
https://html.spec.whatwg.org/multipage/rendering.html */
& :is(button,
input,
select,
textarea,
optgroup,
option) {
font: inherit;
color: inherit;
letter-spacing: inherit;
word-spacing: inherit;
@media (forced-colors: none) {
border: none;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
position: relative;
}
/* Suppression des bordures UA sur img, iframe et fieldset */
& :is(img,
iframe,
fieldset) {
border: none;
}
/* Correctif Firefox : fieldset impose un min-width natif qui casse les layouts flex/grid
https://bugzilla.mozilla.org/show_bug.cgi?id=504622 */
& fieldset {
min-width: 0;
min-inline-size: unset;
}
/*
Suppression de l'espace résiduel sous les images
*/
& img {
display: block;
max-width: 100%;
height: auto;
}
/* Neutralise les espaces de bordure UA des tables */
& table {
border-collapse: collapse;
border-spacing: 0;
}
& td {
empty-cells: show;
}
/* overflow: hidden sur svg:not(:root) est présent dans certains UA stylesheets
mais absent ou inconsistant sur d'autres, règle défensive cross-browser.
https://svgwg.org/svg2-draft/styling.html#UAStyleSheet */
& svg:not(:root) {
overflow: hidden;
}
/* Rétablit le comportement typographique natif des exposants/indices
souvent cassé par un line-height global */
& :is(sub, sup) {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
& sup {
top: -.25em;
}
& sub {
bottom: -.25em;
}
/* del : uniformisation du barré variable */
& del {
position: relative;
text-decoration: none;
color: color-mix(in srgb, currentColor 70%, transparent); /* texte atténué */
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 105%;
height: 0.0625em; /* proportionnel au texte courant */
background: var(--basic-color, black); /* pour texte noir */
transform: translate(-2%, -50%);
}
}
/* dialog : marges, padding et border UA très variables cross-browser.
Le backdrop est volontairement non resetté, opinion projet.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog */
& dialog {
margin: 0;
padding: 0;
border: none;
&::backdrop {
background: none;
}
}
/* details/summary : summary a un display: list-item UA (marqueur triangle)
et un cursor: default inconsistant, on neutralise.
https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements */
& details > summary {
display: block;
cursor: pointer;
list-style: none;
/* Supprime le marqueur webkit */
&::-webkit-details-marker {
display: none;
}
}
/* Correctif taille de fonte monospace : les navigateurs calculent font-size
différemment sur les éléments monospace imbriqués, le doublon monospace,monospace
force le respect de la taille héritée.
https://www.w3.org/TR/CSS22/fonts.html#generic-font-families */
& :is(pre, code, samp, kbd) {
font-family: monospace, monospace;
font-size: 1em;
}
/* blockquote : marges UA variables, padding-inline présent sur certains navigateurs */
& blockquote {
margin: 0;
padding: 0;
}
/* hr : hauteur, border et color très inconsistants cross-browser
https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3 */
& hr {
border: none;
border-top: 1px solid;
height: 0;
overflow: visible;
}
/* abbr[title] : soulignement pointillé et cursor: help absents ou inconsistants
https://www.w3.org/TR/html52/textlevel-semantics.html#the-abbr-element */
& abbr[title] {
text-decoration: underline dotted;
cursor: help;
}
/* Masquage accessible, l'élément reste dans le flux AT mais disparaît visuellement.
Transversal à tout projet.
https://www.w3.org/WAI/WCAG22/Techniques/css/C7
- width/height 1px + !important : valeur minimale non nulle, un width/height à 0
supprime l'élément du flux AT sur certains lecteurs d'écran.
Les !important résistent aux surcharges projet accidentelles.
- clip-path: inset(50%) : équivalent moderne, réduit la zone visible à un point central.
- clip: rect(0,0,0,0) : découpe la zone visible à un rectangle de 0px,
propriété dépréciée mais conservée pour les AT anciens qui ignorent clip-path.
Les 4 valeurs sont top, right, bottom, left relatifs à la position de l'élément.
- z-index: -10 : repousse l'élément sous les autres couches visuelles.
Cas limite : si un ancêtre crée un stacking context avec z-index positif,
le focus clavier peut devenir inatteignable, à surveiller au niveau projet.
- padding/margin 0 !important : évite qu'une valeur positive crée
une zone cliquable visible.
- color: inherit : neutralise un éventuel color global sur [position="absolute"].
- & * { all: inherit !important } : force les enfants à hériter du masquage,
évite qu'un enfant avec display ou visibility explicite soit rendu visible.
Attention : écrase tout, y compris display, à ne pas imbriquer
de composants fonctionnels dans un .sr-only. */
& .sr-only, & .screen-readers {
position: absolute;
width: 1px !important;
height: 1px !important;
line-height: 0;
z-index: -10;
border: 0;
white-space: nowrap;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
color: inherit;
padding: 0 !important;
margin: 0 !important;
& * {
all: inherit !important;
}
}
/* Neutralisation des marges block sur tous les éléments de flux
pour un contrôle total au niveau projet.
Les headings, paragraphes et éléments sémantiques ont des marges UA variables
cross-browser, on les ramène à 0 pour éviter tout écart imprévu. */
& :is(h1, h2, h3, h4, h5, h6,
p,
figure,
pre,
dl, dd,
address) {
margin-block: 0;
}
/* Neutralisation du padding-inline UA sur dd (liste de définition) */
& dd {
padding-inline-start: 0;
}
/* Correctif UA : les navigateurs réduisent font-size du h1 quand il est imbriqué
dans section, article, aside, nav, comportement hérité de HTML5 outline, désormais
abandonné dans la spec mais conservé dans les UA stylesheets.
Lighthouse l'audite sous "H1UserAgentFontSizeInSection".
On rétablit explicitement la valeur UA du h1 racine.
Doit venir après la neutralisation margin-block: 0 du :is() ci-dessus.
https://html.spec.whatwg.org/multipage/rendering.html#sections-and-headings */
& h1 {
margin-block: 0.67em;
font-size: 2em;
}
/* Focus visible, filet de sécurité accessibilité clavier.
WCAG 2.1 SC 2.4.7 (AA) : tout élément focusable doit avoir un indicateur visible.
outline: 2px solid sans couleur explicite = currentColor s'adapte automatiquement
au contexte clair/sombre sans opinion projet. Limite connue : currentColor invisible
si la couleur de l'élément est identique à la couleur de fond environnant.
Dans ce cas, surcharger avec un outline contrasté ou un double outline + box-shadow au niveau projet.
À surcharger au niveau projet (layer base/components) pour affiner couleur,
épaisseur, offset ou remplacer par box-shadow selon le design.
Ne jamais supprimer sans fournir une alternative visible.
Pourquoi dans le reset ? Parce que l'accessibilité n'est pas une option et que ça oblige
à le gérer correctement.
https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html */
& :focus-visible {
outline: 2px solid;
outline-offset: 2px;
box-shadow: 0 0 0 4px Canvas; /* ou white, ou la couleur de fond */
}
& a {
text-underline-position: under;
}
}
/* :root */
} /* @layer reset */
Télécharger kortic-css-reset.css
That's all, Folks !