npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zevra/ui

v0.44.1

Published

Design system Zevra « Encre & Papier » : tokens, CSS des composants et logos officiels.

Readme

Zevra UI : design system « Encre & Papier »

Tokens, CSS des composants et logos officiels de la plateforme Zevra. Source de vérité pour tout ce qui touche à l'identité visuelle : une app qui recopie un hexadécimal au lieu de lire une variable d'ici finira par diverger.

Issu de la passation DA du 23/08/2026. L'atelier où la direction évolue reste le canvas Claude Design « Zevra DA Encre et Papier » ; ce dépôt est ce qui en sort, versionné et consommable.


Installer

pnpm add @zevra/ui

Utiliser

Le CSS

/* tout d'un coup */
@import "@zevra/ui/css";

/* ou à la carte */
@import "@zevra/ui/tokens.css";      /* les variables, indispensable */
@import "@zevra/ui/base.css";        /* reset, typographie, kicker, liens */
@import "@zevra/ui/components.css";  /* boutons, badges, cartes, encarts */
@import "@zevra/ui/layout.css";      /* conteneurs, grilles, sections, chrome */
@import "@zevra/ui/surfaces.css";    /* halos, sols, voiles, manifeste */
@import "@zevra/ui/motion.css";      /* les neuf animations autorisées */
@import "@zevra/ui/icons.css";       /* dimensionnement des icônes */
@import "@zevra/ui/overlays.css";    /* voile, tiroir, modale, notification */
@import "@zevra/ui/forms.css";       /* champs, selects, interrupteurs, dépôt */
@import "@zevra/ui/feedback.css";    /* alertes, badges, états, chargement */
@import "@zevra/ui/navigation.css";  /* menus, barre, fil d'Ariane, pagination */
@import "@zevra/ui/data.css";        /* tableaux, listes, clé-valeur, avatars */

L'ordre compte : les tokens d'abord, le socle ensuite. Toutes les classes sont préfixées zv-. Aucune dépendance, aucun préprocesseur : le paquet fonctionne à l'identique en React, en Astro, en HTML brut et dans un canvas de design.

Le CSS est fluide et responsive là où les maquettes étaient figées à 1440px : .zv-page est plafonnée, pas verrouillée, et un point de rupture unique à 768px reprend les valeurs mobiles du relevé.

Chaque valeur vient des maquettes de la passation. Là où deux planches se contredisaient, l'arbitrage est écrit dans DECISIONS.md.

Les composants React

pnpm add @zevra/ui react
import '@zevra/ui/tokens.css'
import '@zevra/ui/base.css'
import '@zevra/ui/forms.css'

import { Button, Field, Input } from '@zevra/ui/react'

<Field label="ADRESSE E-MAIL" htmlFor="mail" help="Votre adresse professionnelle.">
  <Input id="mail" type="email" />
</Field>
<Button>Envoyer</Button>

Ce ne sont pas des composants qui embarquent leur style : chacun n'est qu'une façade typée et documentée au-dessus des classes zv-. Le CSS reste la source de vérité, il s'importe à part, et un test (tests/react.test.js) vérifie que toute classe qu'un composant peut émettre existe bien dans un module CSS : c'est ce qui empêche les deux de diverger.

Rendu serveur. Un seul module est marqué "use client" : overlays (tiroir, modale, notification), les seuls qui aient besoin du navigateur. Tout le reste se rend au serveur : dans un composant serveur Next comme dans une île Astro à zéro JavaScript. Les composants qui prennent un gestionnaire d'événement (Switch, Seg, Tabs…) restent sans état : c'est un composant client à toi qui détient l'état et les rend.

Astro. @astrojs/react rend ces composants en HTML statique à la compilation, sans JavaScript envoyé, tant qu'aucune directive client:* n'est posée. Un <Button> dans une .astro ne coûte donc rien de plus qu'un <button class="zv-btn"> : avec les props typées et la documentation en prime. ⚠️ En revanche, un composant .astro ne peut pas être passé en enfant d'un composant React : ce sens-là ne fonctionne pas.

Les props sont en français, comme le CSS et les commentaires. Celles qui portent une décision de charte sont documentées à l'endroit où on les lit, et les avertissements ⚠️ signalent ce qui casse le système si on le contourne : pas de simples préférences.

className est accepté partout, mais c'est une sortie de secours, pas un usage courant : une classe locale qui repeint un composant du système est une divergence qui n'a pas dit son nom.

Le catalogue

import { CATALOGUE } from '@zevra/ui/react/catalogue'

Chaque composant y a un exemple rendu, le code qui le produit, et le résumé de ce qu'il fait : et surtout de quand ne pas l'employer. C'est ce que rend la page « Design system » de l'admin, et c'est aussi le banc d'essai du test de dérive : un composant sans exemple n'est ni documenté ni couvert, et le test le refuse.

Deux couches

Le paquet en porte deux, et elles ne servent pas au même usage.

La couche vitrine (base, components, layout, surfaces, motion) compose une page qui se lit : hero, sections, manifeste, cartes produit.

La couche applicative (forms, feedback, navigation, data, icons) compose une interface qui s'opère : champs, alertes, menus, tableaux. Elle est plus dense, et dispose de son échelle propre (--app, --mono).

Une app n'a aucune raison de charger les deux. Sont communs : tokens.css, base.css, icons.css et overlays.css, une page vitrine a besoin d'un tiroir pour son menu mobile autant qu'une application pour sa navigation.

Le menu mobile n'est pas optionnel. Sous 768px, .zv-nav__links doit être masqué (zv-only-desktop) et remplacé par .zv-nav__burger, qui ouvre un .zv-drawer. Sans lui, la navigation devient inatteignable sur téléphone.

Le tableau est à base de grille, pas de <table> : le gabarit de colonnes se pose en --zv-cols sur le conteneur, et sous 768px la rangée s'effondre en carte, chaque valeur reprenant son intitulé.

Le mouvement

Neuf animations, pas une de plus, sous le préfixe zv-anim-. Sept sont pilotées par le défilement, deux tournent en continu :

<span class="zv-halo zv-halo--hero zv-anim-breathe"></span>
<h2 class="zv-h2 zv-anim-rise">…</h2>
<img class="zv-glyph zv-anim-drift" style="--zv-delay: .8s">

Les règles de la passation : une seule courbe, un seul type de mouvement par scène, des entrées toujours vers le haut, et aucun mouvement sur la typographie une fois posée.

Les entrées se jouent une fois par visite (0.12.0) : elles sont armées par html.zv-motion et libérées à l'entrée au viewport par motion.js, import '@zevra/ui/motion.js' une fois par page, et rien d'autre à faire. Remonter la page ne défait rien : l'ancien pilotage view() rejouait les entrées à l'envers sous le lecteur. Sans JavaScript, rien n'est armé et la page s'affiche complète. Un échafaudage de scènes scroll-pilotées pose data-zv-motion-skip sur ses scènes et re-branche les @keyframes sur ses timelines nommées. Seuls zv-anim-progress et zv-anim-dot-active restent au défilement : suivre la lecture est leur métier. Un seul media query neutralise les neuf pour prefers-reduced-motion.

Les tokens en JavaScript

Pour ce qui ne peut pas lire une variable CSS : graphes, images générées, gabarits d'e-mail :

import { couleurs, serieGraphique, polices } from '@zevra/ui'

couleurs.accent      // '#2a2aa8'
serieGraphique       // palette ordonnée, dérivée du spectre

C'est un miroir de tokens.css, pas une seconde source. Un test échoue si les deux divergent.

Les logos

logos/index.json décrit les sept marques : produit, forme, nuance et fond de destination.

Depuis le 24/08/2026, le paquet ne livre plus que les rendus couleur des glyphes (sept depuis la 0.13.0). Les déclinaisons vectorielles (encre, navy, blanc, et les lockups) ont été retirées : elles ne suivaient plus les marques, et celle de Mémoire portait encore un logo abandonné.

import index from '@zevra/ui/logos/index.json'

Nommage : <produit>-<forme>-<nuance>.png, par exemple campus-glyph-orig.png. Produits : zevra, campus, lexform, mcp, memoire. Formes : glyph, mark (Zevra). Nuance : orig.

Ce sont des rendus matriciels de 512 px. Ils tiennent jusqu'à ~380 px d'affichage ; au-delà ils flouteront. Pour les inliner dans un canvas où les ressources distantes sont interdites, l'admin en donne la data-URI.

⚠️ Ce qui manque, et qu'il faudra refaire : les versions monochromes (encre et blanc) et les lockups avec wordmark. La charte les impose (blanc sur navy, mono sous 24 px) mais le paquet ne les porte plus.

Polices

Trois familles Google : Outfit (titres), Public Sans (texte), Space Grotesk (labels capitales).

En production, chargez-les par balise : une @import CSS bloque le rendu :

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@200;300;400;500;600&family=Public+Sans:wght@400;600&family=Space+Grotesk:wght@500;600;700&display=swap">

@import "@zevra/ui/fonts.css" existe pour les prototypes et les maquettes, où la simplicité prime.


Ouvrir un enrobage

Tiroir et modale s'ouvrent par un état porté sur le conteneur. Deux écritures équivalentes, selon le gabarit :

<div class="zv-drawer" data-open>          <!-- HTML, Astro -->
<div class="zv-drawer zv-drawer--open">    <!-- React, Vue -->

⚠️ En React, ne pas écrire data-open={ouvert} : il rend data-open="false", que le sélecteur d'attribut considère comme présent, le panneau resterait ouvert. C'est précisément pour cela que la variante en classe existe.

Fermer sur Échap relève du gabarit : le CSS ne sait pas le faire. En React, Drawer et Modal (@zevra/ui/react) le font déjà, avec le piège de focus, la restitution du focus au bouton d'ouverture et le verrou de défilement : et le hook useOverlay est exporté pour un enrobage maison.

Les règles qui ne se négocient pas

Elles viennent de la passation. Les ignorer produit du « presque Zevra », qui coûte plus cher à corriger qu'à respecter.

Un seul bouton plein par écran. L'indigo porte l'action. Pas de bouton en dégradé, pas de rayon 999.

Jamais de fausse citation, jamais de lorem. Le bloc .zv-quote reste en réserve tant qu'un verbatim client n'a pas été recueilli ; .zv-ui-preview montre de vraies données réalistes : c'est lui qui porte la crédibilité de la page.

Trois cartes de tarif par page au maximum, cinq questions de FAQ, une seule ligne de preuve sociale. Au-delà, la preuve se dilue au lieu de s'accumuler.

Le spectre ne sert qu'à trois choses : le filet de section (56×2 px devant un kicker), la barre de progression, les points de la méthode. Jamais en fond, jamais en texte.

Les logos ne se retouchent pas : ni recoloration, ni resaturation, ni inclinaison. Zone de protection X/2 ; hauteur de capitale du wordmark = 0,30 × glyphe.

Le système est clair par nature. Une surface sombre se fait avec --manifeste et --accent-on-dark, pas en inversant les neutres : les contrastes ont été réglés dans ce sens-là.


Dans un canvas Claude Design

Un canvas interdit les ressources distantes : ni feuille de style externe, ni image par URL. Pour y travailler dans la charte :

  1. coller le bloc :root { … } de src/tokens.css en tête de l'artboard ;
  2. coller les SVG de logo en ligne plutôt que de les référencer.

L'admin Zevra propose les deux au presse-papier (page Plateforme → Design system), c'est le chemin le plus court.

Les polices Google, elles, sont autorisées dans un canvas : la balise <link> ci-dessus fonctionne telle quelle.


Faire évoluer

  1. La décision visuelle se prend dans le canvas Claude Design, avec Pierre.
  2. Elle atterrit ici : src/tokens.css d'abord, puis son miroir tokens/index.js, puis les modules CSS concernés.
  3. pnpm test doit rester vert. Les tests ne jugent pas l'esthétique : ils attrapent le token appelé mais inexistant, la classe définie dans deux modules (où c'est l'ordre de chargement qui tranche à la place de la DA), le second point de rupture glissé au fil de l'eau, et le spectre écrit à la main hors de ses trois emplois autorisés.
  4. Après ajout de logos : node scripts/build-logo-index.mjs, et committer l'index régénéré.
  5. Publier une version : npm version <patch|minor|major> puis npm publish.

demo/index.html exerce la couche vitrine, demo/app.html la couche applicative. Aucune des deux n'est publiée dans le paquet.

reference/ conserve les maquettes HTML, le prototype animé et les fichiers d'origine de la passation. Ce dossier n'est pas publié dans le paquet : il sert d'archive et de référence visuelle, pas de dépendance.