Cotisations 2026
12 adhérents en retard
Le clic est sur le bouton, pas sur la carte : le clavier et le lecteur d’écran y accèdent.
Ce paquet partage une structure, pas une identité : échelles fluides, points de rupture, safe-areas iOS, cible tactile, et des composants React livrés non stylés. La couleur, la typographie de titrage et les arrondis appartiennent à chaque application.
Générique — preset seul
Seize dépôts publics, dont quinze consomment ce
paquet — l’application desktop n’en importe rien, et c’est le champ «
sous-chemins » qui l’a révélé. Cette grille n’est pas une liste tenue
à la main dans la page : elle est engendrée depuis
apps-catalog.js — le fichier qu’importent les
applications elles-mêmes pour s’afficher les unes les autres. Ce qui
serait faux ici serait faux dans leurs pieds de page.
Recherche unifiée en haut de page (CtrlK)
Combien d’apps importent chaque symbole — pour guider une campagne, pas seulement lire le README. Les copies locales (dette) apparaissent en dessous quand le relevé en a trouvé.
| Symbole | Apps | Part |
|---|
| Symbole | Apps |
|---|
Pour une app du relevé : présent, recopié localement, ou absent — avec un lien vers la démo concernée.
Le domaine et la maturité sont éditoriaux, saisis à
la main : ils disent une intention, pas une version publiée. La
persistance et les sous-chemins sont
relevés dans le code de chaque app — les
import et les extends réels, pas une
intention —, et laissés vides quand ils ne l’ont pas été : un filtre
qui affiche « non relevé » vaut mieux qu’une donnée inventée. Le
filtre Consomme répond ainsi à la question qu’un design
system doit se poser en premier : qui utilise vraiment quoi ?
components.css n’a qu’un adoptant sur seize. Les
pastilles sont peintes avec la primaire réelle de chaque application,
jamais avec une icône distante : le showroom ne fait aucune requête
réseau.
Tout ce que le paquet exporte, en une grille. Un test compare cette
liste aux exports réels de react/index.js : ce qui n’est
pas ici est soit couvert par une autre fiche, soit nommément exclu —
c’est ainsi que les neuf hooks, absents de leur propre vitrine, ont
été repérés.
Tous les tokens ci-dessous viennent de
tailwind-preset.css et sont donc identiques dans toutes
les applications de la famille. Les valeurs affichées sont
calculées en direct : redimensionnez la fenêtre pour
voir jouer les clamp().
| Token | Valeur | Calculé | Rendu |
|---|---|---|---|
--text-fluid-xs |
clamp(0.7rem, 1.6vw, 0.8125rem) |
— | Aa — Portez ce vieux whisky |
--text-fluid-sm |
clamp(0.8125rem, 1.9vw, 0.95rem) |
— | Aa — Portez ce vieux whisky |
--text-fluid-base |
clamp(0.9rem, 2.2vw, 1.05rem) |
— | Aa — Portez ce vieux whisky |
--text-fluid-lg |
clamp(1rem, 2.6vw, 1.25rem) |
— | Aa — Portez ce vieux whisky |
--text-fluid-xl |
clamp(1.15rem, 3vw, 1.5rem) |
— | Aa — Portez ce vieux |
--text-fluid-2xl |
clamp(1.35rem, 4.2vw, 2rem) |
— | Aa — Portez ce vieux |
Côté Tailwind : text-fluid-sm,
text-fluid-2xl… La colonne « Rendu » utilise la police de
titrage du thème courant.
| Token | Rendu |
|---|---|
--font-sans |
Interface système — 0123456789 |
--font-mono |
const total = 42; // 0O1lI |
--ds-font-display
(app)
|
Titrage de l’application |
Le preset ne fournit que sans et mono
(piles système, zéro requête réseau). Les polices de titrage (Fredoka,
Plus Jakarta Sans, DM Sans, Inter…) sont chargées par chaque
app ; ce showroom ne les télécharge pas et retombe sur la pile
système si elles ne sont pas installées localement.
| Token | Valeur | Calculé | Aperçu |
|---|---|---|---|
--spacing-fluid-sm |
clamp(0.5rem, 1.4vw, 0.75rem) |
— | |
--spacing-fluid-md |
clamp(0.75rem, 2.4vw, 1.25rem) |
— | |
--spacing-fluid-lg |
clamp(1rem, 3.2vw, 1.75rem) |
— |
En rem et non en px : les ruptures suivent
la taille de police du navigateur. Une police par défaut agrandie
décale donc bien la mise en page — sinon c’est une régression
d’accessibilité.
env(safe-area-inset-*). Utilitaires :
pt-safe, pb-safe, px-safe,
py-safe, top-safe,
bottom-safe-3…
| Inset | Valeur mesurée |
|---|---|
safe-area-inset-top |
— |
safe-area-inset-bottom |
— |
safe-area-inset-left |
— |
safe-area-inset-right |
— |
Ces valeurs valent 0 partout sauf sur un appareil à encoche / barre
d’accueil, et uniquement si le document déclare
viewport-fit=cover. Les variantes *-safe-3
ajoutent 0,75 rem à l’inset système.
Le preset pose aussi :focus-visible →
outline: 2px solid currentColor avec
outline-offset: 2px. Parcourez la page au clavier
(Tab) : l’anneau suit la couleur du texte, donc reste
lisible dans tous les thèmes.
Le preset partagé n’expose aucune couleur. C’est
volontaire : chaque application définit sa palette dans son propre
@theme. Le showroom mappe ces palettes sur un jeu de
rôles sémantiques communs — c’est ce jeu que la bascule de thème
réécrit.
Valider une palette en alternant les deux schémas est un mauvais outil. Les voici simultanément, pour le thème sélectionné.
Choisir deux habillages pour comparer leurs primaires et surfaces dans le schéma courant — utile avant une revue visuelle croisée.
Seuls les rôles qui diffèrent — pour une revue croisée sans lire deux panneaux entiers.
| Rôle | App A | App B |
|---|
Palette du thème courant, lue via getComputedStyle.
Ces six composants n’ont pas été inventés : ils ont été extraits de ce que plusieurs apps avaient déjà réécrit chacune de leur côté. Les matrices ci-dessous montrent les combinaisons — c’est là que vivent les régressions, pas dans l’instance isolée.
loading pose aria-busy
et désactive le bouton : afficher un indicateur sans
bloquer laissait passer le double-clic dans deux apps.
Le tone dit une intention, pas une couleur — la
teinte réelle vient du thème de l’app.
Sert aux relances de cotisation.
En euros, deux décimales.
Le montant doit être positif.
En erreur, aria-describedby référence l’aide
et le message — les copies locales remplaçaient l’une
par l’autre, faisant disparaître la consigne au pire moment.
La tendance porte une flèche et un libellé lu par les lecteurs d’écran : la couleur ne suffit pas à distinguer une hausse d’une baisse.
12 adhérents en retard
Le clic est sur le bouton, pas sur la carte : le clavier et le lecteur d’écran y accèdent.
padding={false} : l’image touche les bords.
Une carte est une surface, pas un contrôle : l’action va dans
action (un élément focusable à l’intérieur), jamais sur
la carte entière. CardHeader rend un vrai titre, pour que
la structure du document survive à la mise en page.
Les barres sont aria-hidden ; seul le conteneur est
annoncé (role="status"). Sinon un lecteur d’écran énumère
« chargement » une fois par barre.
Essayez au clavier : Tab boucle dans le panneau, Échap ferme, et le focus revient sur le bouton d’origine — les trois comportements qu’aucune copie locale n’avait au complet.
Les matrices montrent des combinaisons choisies ; celle dont vous avez besoin n’y est pas forcément. Réglez les props ci-dessous : le rendu suit, et l’appel React à copier se réécrit avec — y compris les attributs d’accessibilité qu’ils entraînent.
Le DOM engendré ici est écrit à la main, comme dans toute cette page —
le showroom n’embarque pas React. C’est bien le
même balisage que produit le composant, et il est
habillé par le components.css réel du paquet ; mais c’est
l’extrait de code, pas l’aperçu, qui fait foi.
Les composants du paquet ne posent que des attributs
data-dwc : aucune classe, aucun style imposé. Chaque app
les habille avec sa propre CSS. Ci-dessous, le DOM exact produit par
chaque composant, habillé par le thème courant.
État vide avec action suivante.
Aucune donnée pour l’instant
Créez une première entrée pour voir apparaître vos statistiques.
[data-dwc="empty-state"] — conteneur[data-dwc="empty-state-icon"][data-dwc="empty-state-title"][data-dwc="empty-state-desc"][data-dwc="empty-state-action"]
Erreur récupérable. severity distingue le temporaire
du permanent ; role passe de alert à
status.
[data-dwc="error-banner"][data-severity="error|warning|info"]
[data-dwc="error-banner-message"][data-dwc="error-banner-retry"][data-dwc="error-banner-dismiss"]
État de synchronisation. Le compteur n’apparaît que sur
pending.
[data-dwc="sync-status"][data-status="synced|pending|offline|error"]
UI de repli par défaut (évite l’écran blanc). Le bouton «
sauvegarde » n’apparaît que si onDownloadBackup est
fourni.
Une erreur est survenue.
[data-dwc="error-boundary"][data-dwc="error-boundary-title"][data-dwc="error-boundary-reset"][data-dwc="error-boundary-backup"]
Bannière passive (role="region", pas
dialog : elle ne piège pas le focus). Deux visages :
le bouton natif, et la marche à suivre là où le navigateur n’en
expose aucun.
Installer l’application
Ajoutez cette application à votre écran d’accueil : accès rapide, hors-ligne.
Installer l’application
Touchez le bouton Partager, puis « Sur l’écran d’accueil ».
[data-dwc="pwa-install-prompt"][data-dwc="pwa-install-title"][data-dwc="pwa-install-desc"][data-dwc="pwa-install-actions"][data-method="prompt|instructions"][data-platform="ios|safari|generic"]Partage natif, repli presse-papiers. L’annulation n’affiche rien : fermer la feuille de partage n’est pas un échec.
[data-dwc="share-button-group"][data-dwc="share-button"][data-dwc="share-button-status"]
Le tracé est aria-hidden : la donnée est dite en
toutes lettres dans un texte à côté. Un graphique qui ne dit rien
ne dit rien à personne.
[data-dwc="sparkline"][data-dwc="sparkline-text"][data-dwc="bars"][data-dwc="gauge"]
registerSW est reçu en prop, plus importé en dur : le
module a rejoint le barrel. Le bouton
UpdateButton des réglages, lui, s’en passe.
[data-dwc="update-banner"][data-dwc="update-banner-title"][data-dwc="update-banner-actions"][data-dwc="update-banner-update"][data-dwc="update-banner-dismiss"]Sept apps sur seize en avaient un, tous différents. Le focus s’ouvre sur Annuler — jamais sur la confirmation.
[data-dwc="confirm"][data-dwc="confirm-panel"][data-dwc="confirm-title"][data-dwc="confirm-body"][data-dwc="confirm-actions"][data-dwc="confirm-cancel"][data-dwc="confirm-confirm"]
Deux régions vivantes montées en permanence, et aucun rôle sur le
message : c’est ce qui évite la double annonce constatée dans deux
apps. Une action facultative y met un bouton — «
Annuler » plutôt que « êtes-vous sûr ? » — et porte la durée
de vie à huit secondes.
[data-dwc="toast-viewport"][data-dwc="toast-region"][data-dwc="toast"][data-tone][data-dwc="toast-message"][data-dwc="toast-action"][data-dwc="toast-close"]
Sept apps sur seize en ont une. L’onglet courant n’est jamais
distingué par la seule couleur : aria-current, un
trait, et un « Page actuelle » lu mais non vu.
[data-dwc="bottom-nav"][data-dwc="bottom-nav-item"][data-current][data-dwc="bottom-nav-icon"][data-dwc="bottom-nav-label"][data-dwc="bottom-nav-badge"][data-dwc="bottom-nav-more"][data-dwc="bottom-nav-drawer"]
Neuf apps ont un en-tête. Le titre EST le h1 de la
page ; le retour est un lien quand il a une destination, un bouton
quand il n’a qu’une action — nommé « Retour » en sept langues.
[data-dwc="app-header"][data-sticky][data-dwc="app-header-row"][data-dwc="app-header-back"][data-dwc="app-header-leading"][data-dwc="app-header-title"][data-dwc="app-header-actions"][data-dwc="app-header-extra"]La coquille assemblée : lien d’évitement, en-tête, contenu borné, barre basse. Le pied de page n’y est PAS — Accueil et À propos seulement.
Notes
Contenu de la vue.
[data-dwc="app-shell-skip"][data-dwc="app-shell"][data-dwc="chrome-prefs"]Centré, borné à un palier de largeur, zones sûres iOS comprises — celle du bas surtout, sans laquelle le dernier bouton d’une vue colle à la barre d’onglets. Promu de badminton et molkky.
width="sm" — 28 rem, centré, marges sûres.
[data-dwc="page-container"][data-width="sm|md|lg|xl|full"]
[data-dwc="page-container"][data-padding="none"]
Quatre apps avaient le même écran : deux champs, un bouton, une
erreur traduite. Le formulaire est présentationnel — l’appelant
fait signIn et redonne busy et
error ; children et footer
portent le reste (inscription, passkey, mot de passe oublié).
[data-dwc="login-form"][data-mode="signin|signup"]
[data-dwc="login-form-title"][data-dwc="login-form-error"][data-dwc="login-form-footer"]L’étape TOTP au login, promue de mister-doc et miss-uwh : clavier numérique, code reçu proposé par le système, voie de secours et déconnexion seulement quand l’appelant les fournit.
[data-dwc="mfa-challenge"][data-mode="totp|recovery"]
[data-dwc="mfa-challenge-hint"][data-dwc="mfa-challenge-error"][data-dwc="mfa-challenge"] [data-action="switch"]
[data-dwc="mfa-challenge"] [data-action="sign-out"]
Cinq apps en ont un. Celui-ci parcourt les trois états de
useTheme : une bascule à deux états interdit de
revenir à « système ».
[data-dwc="theme-toggle"][data-dwc="theme-toggle"][data-theme-state]
[data-dwc="theme-toggle-icon"][data-dwc="theme-toggle-label"]Groupe accessible thème + langue (ou densités) : une seule étiquette pour ce que le hub et plusieurs apps juxtaposent déjà.
[data-dwc="chrome-prefs"]Lien code source + lien sponsor, ouverture sécurisée.
[data-dwc="app-footer"][data-dwc="footer-source"][data-dwc="footer-sponsor"]
L’écran À propos famille : installation, intro métier, grille des
sœurs (showSource={false}), pied de page. Promu du
squelette.
Intro métier (carte titre) — puis la grille, puis le pied.
[data-dwc="family-about"]
Le numéro de version, injecté au build par
vite-version : ce qui tourne, ce qui vient
d’être installé, ce qui est en ligne.
Version 3.13.0 Compilée le 26 août 2026 · 104c944 Version 3.14.0 disponible
[data-dwc="app-version"][data-dwc="app-version-label"][data-dwc="app-version-value"][data-dwc="app-version-details"][data-dwc="app-version-updated"][data-dwc="app-version-available"]
Grille « nos autres applications », alimentée par
apps-catalog.js, avec badge de maturité.
[data-dwc="family-apps"][data-dwc="family-links"],
[data-dwc="family-source"],
[data-dwc="family-sponsor"]
[data-dwc="family-app-list"],
[data-dwc="family-app-item"],
[data-dwc="family-app"],
[data-dwc="family-app-repo"]
[data-dwc="family-app-icon"],
[data-dwc="family-app-body"],
[data-dwc="family-app-name"],
[data-dwc="family-app-desc"],
[data-dwc="family-app-arrow"]
[data-dwc="maturity"][data-maturity="…"]
Les icônes réelles sont chargées depuis les GitHub Pages de chaque app. Ce showroom reste hors ligne : il exerce le chemin de repli (initiale du nom) du composant.
Les sections précédentes montrent chaque composant seul. Elles ne disent jamais lequel prendre quand deux conviennent — et c’est pourtant là qu’on hésite. Chaque arbre tient en deux à quatre branches ; au-delà, ce n’est pas l’arbre qui manque de place, c’est l’API qui est sous-spécifiée.
Neuf exports du barrel, soit près de la moitié de la surface React du paquet - et aucun n’apparaissait ici jusqu’à présent. Ils n’ont pas de démo : un hook ne se regarde pas. Ils ont une signature, une phrase, et le piège qui va avec.
Hooks et utilitaires exportés par le paquet
useTheme mérite une mention à part : c’est le contrat que
reproduit le sélecteur en haut de cette page -
light | dark | system persisté sous
dwc_theme, attribut data-theme posé sur
<html>.
Mesurés sur cette page, dans le thème courant, à
chaque changement de thème ou de taille de fenêtre. Ce ne sont pas des
valeurs recopiées : elles sortent de
getBoundingClientRect et de
getComputedStyle.
Quatre filets successifs, du clavier du développeur au navigateur de l’utilisateur. Aucun ne suffit seul, et c’est pour ça qu’ils sont quatre.
| Outil | Quand | Ce qu’il attrape | Réglage partagé |
|---|---|---|---|
eslint-plugin-jsx-a11y |
À l’écriture |
Les fautes visibles dans le JSX : image sans alt, gestionnaire
de clic sur un <div>, label orphelin.
Règles recommended en warn.
|
eslint-react |
| axe-core + Playwright | En CI, navigateur réel |
Violations WCAG sur le DOM rendu, tags
wcag2a, wcag2aa,
wcag21a, wcag21aa. Suite présente
dans 11 apps.
|
playwright-a11y,
templates/e2e/a11y.spec.ts
|
| Lighthouse CI | En CI, sur le build |
Score d’accessibilité global, en
error sous
0,9 — la PR est bloquée. Configuré dans
12 apps.
|
pwa-lighthouse (workflow) |
| Le design system | À l’exécution |
Ce qu’un audit ne rattrape pas parce que ça se décide à la
conception : cible tactile de 2,75 rem imposée,
:focus-visible qui suit la couleur du texte,
breakpoints en rem,
prefers-reduced-motion, sévérités jamais portées
par la seule couleur.
|
tailwind-preset.css, components.css
|
axe-core ne détecte que 30 à 50 % des défauts, et Lighthouse encore moins. Un score de 100 ne dit pas qu’une appli est utilisable au clavier ni qu’un lecteur d’écran l’annonce correctement — d’où les deux dernières lignes du tableau, et les mesures ci-dessous.
Seuil de 2,75 rem (≈ 44 px), recommandation WCAG 2.5.5 / iOS. Le
Button partagé le tient même en taille sm —
les variantes locales des apps descendaient à 32 px.
Seuil AA : 4,5:1 pour le texte courant, 3:1 pour le grand texte et les éléments d’interface. Un thème d’app qui échoue ici échoue aussi dans l’app.
Ratios calculés sur les palettes déclarées (sans habiller la page) — pour prioriser une campagne, pas une app à la fois. Seuil AA 4,5:1.
| App | text / surface | primary / on | soft / text |
|---|
Sous « contraste élevé » de Windows, le navigateur
remplace d’autorité les couleurs de texte, de fond et
de bordure, et supprime les ombres. Tout
components.css reposant sur des variables et des
color-mix(), ce rendu-là ne se déduit pas : il se
regarde.
Sans les correctifs
Avec les correctifs livrés
Ces deux panneaux sont une émulation, pas le vrai
mode : la page reproduit à la main ce que fait le navigateur —
couleurs ramenées à la palette système « Aquatique » de Windows,
ombres supprimées, et transparent
laissé intact, car le forçage ne le touche pas. C’est
précisément ce dernier point qui vide le bouton primaire à gauche. Le
rendu authentique s’obtient en activant le thème de contraste dans le
système, ou via
Rendering › Emulate CSS media (forced-colors) dans les
outils de développement.
Chaque tuile est peinte avec la palette réelle d’une application, dans le schéma en cours. Un clic habille la page entière — le même geste que Habiller la page sur une carte de la vitrine. L’aperçu en grand reprend alors les composants partagés dans cet univers. C’est la même page, le même CSS : seules les treize variables du contrat changent.
Ce sont des aperçus générés, pas des captures d’écran.
Ils sont peints en direct avec la palette réelle de chaque app et les
composants réels du paquet — ils montrent donc fidèlement le design
system dans chaque univers, mais pas les écrans des applications. Une
vraie capture déposée dans showroom/screenshots/ et
déclarée dans screenshots.js prend automatiquement la
place de l’aperçu.
Relevé dans le package.json et le code source des
applications, pas dans une note d’intention. Ce qui suit décrit ce qui
est réellement installé et appelé.
Trois familles de persistance coexistent, et c’est délibéré : une app qui n’a pas besoin de compte utilisateur n’embarque pas de backend.
| Persistance | Applications | Fonctionnalités utilisées |
|---|---|---|
| Supabase | 9 apps — carbook, lookhouse, uwh, doc, footcoach, mölkky, family-map, miss-koh, settle |
Postgres + RLS (deny-by-default), Auth,
Realtime (.channel()), fonctions SQL
(.rpc()), Storage, Edge Functions
(functions.invoke), migrations versionnées — de 3
(footcoach) à 26 (doc).
|
| Firebase | 3 apps — miss-ticket, puzzle, qowa | Realtime Database pour le temps réel à faible latence (puzzle, qowa), Firestore pour les données structurées (miss-ticket, qowa), Auth, Storage, Cloud Functions. |
| Local-first | 6 apps — badminton, contraction, devises, dice, genius, cim10 |
localStorage / IndexedDB seuls. Aucun compte,
aucune donnée qui sort du téléphone — souvent le bon choix, et
la meilleure réponse RGPD.
|
miss-supaboss est à part : elle
pilote d’autres comptes Supabase via un backend Node et un
jeton personnel, sans client Supabase côté navigateur. Le paquet
fournit par ailleurs un keep-alive (workflow
réutilisable + templates/supabase/keep-alive.sql) contre
la mise en pause automatique du plan gratuit.
lucide-react est le standard famille :
10 apps sur 15 l’utilisent. Icônes SVG tree-shakées
(seules celles importées sont embarquées), strokeWidth et
size ajustables, et currentColor qui suit
les tokens du thème.
Deux réserves apprises à l’usage : lucide 1.x a
retiré les icônes de marque — le logo GitHub des
composants partagés est un SVG inline ; et une icône décorative doit
porter aria-hidden, le libellé accessible vivant sur le
bouton qui la contient. Les 5 apps sans lucide utilisent des SVG
inline maison.
Une seule app en a besoin : miss-lookhouse (veille immobilière), avec Leaflet 1.9 — affichage des biens et éditeur de zone par polygone. Tuiles OpenStreetMap, avec attribution et sans clé d’API : pas de compte tiers, pas de quota, pas de traceur.
Pas de standard famille imposé ici — un seul cas d’usage ne justifie pas une règle. Si une deuxième app cartographie, Leaflet + OSM est le point de départ à reprendre.
3 apps sur 15 intègrent
Rive
— animations vectorielles pilotées par machines à états,
plutôt que par des Lottie/GIF joués en boucle. Et sur deux runtimes
différents : @rive-app/react-canvas pour miss-badminton
et mister-mölkky, @rive-app/react-webgl2 pour
miss-genius.
| Application | Runtime | Garde-fou avant de charger le moteur |
|---|---|---|
| miss-badminton | react-canvas 4.28 |
import.meta.glob à la compilation : fichier
absent = aucun fetch, donc aucun 404. Puis
vérification des 4 octets d’en-tête
(RIVE) avant de monter le canvas.
|
| mister-mölkky | react-canvas 4.28 |
Même sonde d’en-tête, plus un React.lazy sur le
module du canvas : le WASM (~200 ko) ne descend que si un
fichier valide existe.
|
| miss-genius | react-webgl2 4.18 |
Trois sorties vers le visuel statique : pas de
src, événement loaderror, ou
prefers-reduced-motion. Le moteur WebGL n’est
alors jamais chargé.
|
Deux constats qui ne se voient pas dans un package.json.
Aucun fichier .riv n’est livré — les
trois dossiers ne contiennent qu’un README : en production, les trois
apps affichent aujourd’hui leur repli statique, et c’est le chemin le
mieux testé. Et
aucune n’utilise le wrapper du paquet
(RiveAnimation, sous-chemin /react/rive) :
il enveloppe le composant <Rive>, alors
que les trois ont besoin du hook useRive pour
atteindre l’instance — entrées de machine à états, événements
d’erreur, cleanup(). Un wrapper qui ne donne pas accès à
l’instance ne sert à rien ici ; c’est une inadéquation d’API, pas un
oubli.
| Outil | Portée | Config partagée |
|---|---|---|
| Vitest 4 | Unitaire et composants, en jsdom. Présent dans les 13 apps, de 2 à 54 fichiers de test. | vitest-base, vitest-setup |
| Playwright | Parcours critiques de bout en bout, dans un vrai navigateur. 11 apps. | playwright-base |
| axe-core | Audit d’accessibilité automatisé, branché sur Playwright. 10 apps. |
playwright-a11y,
templates/e2e/a11y.spec.ts
|
| Vitest Browser Mode | Composants dans un navigateur réel plutôt qu’en jsdom. Disponible, utilisé par aucune app à ce jour. | vitest-browser-base |
axe-core ne détecte qu’une partie des défauts d’accessibilité — typiquement 30 à 50 %. Il ne remplace ni le parcours au clavier, ni les contrôles de contraste de la section précédente.
Deux lignes dans le CSS principal de l’app suffisent à hériter de tout
ce qui précède. La palette, elle, se déclare ensuite dans le
@theme du projet.
/* src/index.css */
@import 'tailwindcss';
@import '@mister-guiiug/dev-pwa-config/tailwind-preset.css';
/* Identité PROPRE à l'application — jamais dans le preset partagé. */
@theme {
--color-primary: #1758ba;
--color-accent: #edc715;
--font-display: 'Fredoka', system-ui, sans-serif;
--radius-card: 1.1rem;
}
Rappel de priorité : un @theme placé
après l’import écrase le preset ; du CSS non «
layered » l’emporte sur la couche base.
Le schéma clair/sombre est piloté par le hook
useTheme() (light | dark | system), qui pose
data-theme sur <html> — ou la classe
.dark avec l’option attribute: 'class'. Le
sélecteur en haut de cette page reproduit exactement ce contrat.
Le panneau garde le focus : Tab revient au premier élément après le dernier.