typographyMay 3, 202616 min read

Polices variables en production : comment les livrer sans massacrer les performances

Un guide de terrain sur les polices variables en production. Les quatre axes qui valent d'être livrés, la stratégie de subsetting qui divise le poids du fichier par deux, et la recette de chargement qui empêche la page de sauter.

By Boone
XLinkedIn
variable fonts in production

Polices variables en production : comment les livrer sans massacrer les performances

Une police variable, c'est un seul fichier avec une infinité de graisses, et les équipes qui les livrent bien économisent des octets tout en gagnant en expressivité. Celles qui les livrent mal chargent des polices de 800 kilo-octets et bloquent le premier rendu pour des animations que personne ne verra. La différence ne tient pas au choix de la police. Elle tient à la stratégie de production.

Une police variable, c'est un seul fichier avec une infinité de graisses

Une police variable embarque toutes les graisses, inclinaisons et largeurs dans un seul fichier, avec des axes entre lesquels on peut interpoler. C'est tout l'argument de vente, et aussi tout le compromis. Une seule requête réseau remplace de quatre à sept requêtes de police statique. Le fichier est plus lourd que n'importe quelle graisse statique prise isolément, mais plus léger que l'ensemble complet des graisses statiques réunies. Voilà le deal.

Le format est de l'OpenType avec des données de variation embarquées. Le navigateur interpole entre les extrémités des axes au moment du rendu, ce qui vous donne font-weight: 350 ou font-weight: 723 gratuitement, et pas seulement les entiers que la fonderie a choisi de livrer. C'est ce qui rend possible le motif d'animation de graisse au survol, et c'est aussi ce qui pousse une implémentation négligente à charger un fichier de 600 kilo-octets pour un site qui n'utilise que deux graisses.

Les quatre axes qui valent d'être livrés

Graisse, inclinaison, corps optique, largeur. La plupart des sites en production n'ont besoin que de deux ou trois de ces axes, et livrer plus d'axes que vous n'en utilisez, c'est gaspiller des octets.

La spécification OpenType définit cinq axes enregistrés : la graisse (wght), la largeur (wdth), l'inclinaison (slnt), l'italique (ital) et le corps optique (opsz). Au-delà, les fonderies peuvent définir des axes personnalisés pour tout et n'importe quoi, du grade à l'expressivité. Les axes personnalisés sont amusants sur un spécimen typographique. Ils valent presque jamais leur poids en production. Tenez-vous-en aux quatre axes enregistrés, sauf si votre design exige explicitement autre chose.

AxePropriété CSSPlage typiqueQuand l'utiliser
Graisse (wght)font-weight100–900Presque toujours
Largeur (wdth)font-stretch75%–125%Éditorial, titres responsives
Inclinaison (slnt)font-style: oblique Xdeg-15deg à 0degVrai remplacement de l'italique ou animation
Corps optique (opsz)font-optical-sizing6–144Titrage et texte courant depuis un seul fichier
Concept voxel montrant les quatre axes d'une police variable (graisse, largeur, inclinaison et corps optique) comme dimensions distinctes d'un seul fichier de caractère
Concept voxel montrant les quatre axes d'une police variable (graisse, largeur, inclinaison et corps optique) comme dimensions distinctes d'un seul fichier de caractère

La graisse est le cheval de labour

L'axe de graisse remplace les quatre à sept fichiers de graisses statiques que livrent la plupart des sites, et c'est l'axe qui rentabilise à lui seul les polices variables. Un site qui livre Regular, Medium, SemiBold, Bold et ExtraBold en fichiers statiques fait cinq requêtes de police. Une police variable avec un axe de graisse en fait une, et livre gratuitement toutes les valeurs intermédiaires.

Le CSS, c'est font-weight: 300 jusqu'à font-weight: 900 sur n'importe quel élément. Les valeurs intermédiaires comme font-weight: 450 sont valides et s'affichent correctement. Vérifiez la documentation de la plage d'axe chez la fonderie avant de supposer qu'une police variable couvre toute la plage de 100 à 900. Certaines livrent une plage plus étroite et plafonnent en silence, ce qui produit des paliers subtils mais moches au lieu de transitions fluides.

L'inclinaison débloque les vrais italiques

Un vrai axe d'inclinaison vous permet de passer de la verticale à l'italique à n'importe quelle valeur, ce que la plupart des paires de polices statiques ne peuvent pas faire sans un second fichier. La distinction entre slnt (inclinaison) et ital (italique) compte : l'inclinaison est une plage continue de la verticale à l'oblique, tandis que l'italique est un interrupteur binaire qui bascule vers un dessin italique distinct, corrigé optiquement.

Pour le texte courant, l'axe italique avec un vrai italique optique est presque toujours le bon choix. Pour les titres que vous comptez animer, l'inclinaison est l'axe qu'il vous faut. Le CSS est font-style: oblique -10deg, la valeur en degrés correspondant à la plage de l'axe. Testez l'inclinaison à la taille réelle de votre titre avant de vous engager sur une valeur. Les angles extrêmes sont moins flatteurs aux petites tailles que ne le laisse croire le spécimen de la fonderie.

Le corps optique garde le titrage vivant

Le corps optique ajuste le contraste, la hauteur d'x et le niveau de détail selon la taille de rendu, ce qui revient à intégrer dans une seule police la différence entre une face de titrage et une face de texte. Un caractère dessiné pour du texte en 12px a des détails simplifiés, des contreformes ouvertes et un espacement généreux. Une coupe de titrage à 80px a un contraste plus tranché, un espacement plus serré et des détails plus raffinés. Le corps optique vous donne les deux depuis un seul fichier.

La propriété CSS est font-optical-sizing: auto, qui laisse le navigateur appliquer les bons réglages optiques selon la font-size déclarée. Vous pouvez aussi le régler à la main avec font-variation-settings: 'opsz' 72 pour un titre en 72px. Les polices qui prennent correctement en charge cet axe, Inter Display contre Inter Text étant l'exemple canonique, ont été conçues avec le corps optique comme fonctionnalité de premier rang. Les polices qui ajoutent un axe opsz après coup offrent rarement une différence visuelle significative. Sachez laquelle vous achetez.

La largeur est l'axe éditorial

Un axe de largeur vous permet de comprimer les titres et d'étendre les citations mises en exergue sans changer de police, et c'est le geste qui fait chanter les mises en page éditoriales. La propriété CSS est font-stretch, et elle prend des pourcentages : font-stretch: 75% pour du condensé, font-stretch: 125% pour de l'étendu.

Cet axe mérite son poids sur les sites aux mises en page éditoriales responsives. Un titre en 120px sur desktop voudra peut-être être condensé pour tenir dans la colonne à 375px sur mobile, et un axe de largeur règle ça avec une simple requête @media au lieu d'un second chargement de police. Roboto Flex livre l'une des plus larges plages d'axe de largeur dans une police variable prête pour la production, et elle vaut l'étude si vous comptez utiliser cet axe sérieusement. Pour les sites marketing avec des titres à largeur fixe, oubliez-le complètement.

Variable ou statique, l'arbre de décision

Les polices variables gagnent quand vous utilisez trois graisses ou plus, quand vous animez la typographie, ou quand vous avez besoin du corps optique. Sinon, restez en statique. Le point d'équilibre se situe généralement entre deux et trois graisses, et le calcul change selon vos exigences de couverture linguistique.

ScénarioMeilleur choixRaison
1 à 2 graisses, pas d'animationStatiqueMoins d'octets au total
3 graisses ou plusVariableUn fichier bat trois fichiers
Animation de graisse ou d'inclinaisonVariableImpossible autrement
Corps optique nécessaireVariableÉvite un second chargement de police
Latin étroit uniquement, une seule graisseStatiqueAucun intérêt à la variable
Multi-locales, nombreuses languesVariable avec subsettingUn fichier de base, des extensions par locale

Un site qui livre Regular et Bold s'en sortira probablement mieux avec deux fichiers statiques totalisant 40 à 60 kilo-octets qu'avec une police variable de 120 à 200 kilo-octets. Le calcul bascule nettement en faveur de la variable dès que vous passez trois graisses ou que vous ajoutez de l'animation au cahier des charges.

Le subsetting coupe le fichier en deux

Le subsetting élimine les glyphes que vous n'utilisez pas, et sur un site marketing, cela signifie qu'une police variable de 600 kilo-octets tombe à 200 kilo-octets sans changement visible. Une police variable complète livre chaque glyphe dessiné par la fonderie : latin, latin étendu, cyrillique, grec, opérateurs mathématiques, symboles monétaires, et des ligatures que vous n'utiliserez jamais. Une police variable subsettée ne livre que ce dont votre contenu a besoin.

L'outil pour ça, c'est pyftsubset de la bibliothèque fonttools. Une commande de subset latin basique :

bash
pyftsubset font.woff2 \ --output-file=font-subset.woff2 \ --flavor=woff2 \ --unicodes=U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD \ --layout-features="kern,liga,calt,rlig"

Gardez les fonctionnalités de mise en page kern, liga, calt et rlig. Supprimez tout le reste, sauf si le design l'utilise explicitement. Passez la sortie dans un compresseur WOFF2 si l'outil n'a pas géré la compression automatiquement.

Concept voxel illustrant un fichier de police variable rogné par le subsetting, qui retire les plages de glyphes inutilisées pour ramener une police de 600 Ko à 200 Ko
Concept voxel illustrant un fichier de police variable rogné par le subsetting, qui retire les plages de glyphes inutilisées pour ramener une police de 600 Ko à 200 Ko

Latin, latin étendu et plages de langues

La bonne stratégie de subsetting, c'est un fichier latin plus un fichier d'extension de langue par locale, chargés uniquement en cas de besoin. La plupart des sites marketing et produit servent principalement du contenu latin. Livrez le subset latin par défaut, et chargez les jeux de caractères étendus via le descripteur unicode-range dans @font-face, qui dit au navigateur de ne récupérer un fichier que s'il rencontre des caractères correspondants sur la page.

css
@font-face { font-family: 'YourFont'; src: url('/fonts/yourfont-latin.woff2') format('woff2'); font-weight: 100 900; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: 'YourFont'; src: url('/fonts/yourfont-latin-ext.woff2') format('woff2'); font-weight: 100 900; font-display: swap; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; }

Le navigateur ne télécharge que les fichiers qui correspondent aux caractères de la page en cours. Une page française charge le fichier latin étendu. Une page anglaise, non. C'est ainsi que Google Fonts livre à grande échelle, et c'est le bon motif à copier pour vos propres polices variables.

Charger sans saccades

Une police variable qui se charge après le premier rendu décalera la mise en page, et la recette de chargement, c'est preload plus font-display swap plus une police de repli aux métriques ajustées. Ce n'est pas optionnel. Une police variable est plus lourde qu'une seule graisse statique, donc elle met plus de temps à arriver, donc la fenêtre de décalage de mise en page est plus large.

Le lien de preload va dans le <head>, avant toute feuille de style :

html
<link rel="preload" href="/fonts/yourfont-latin.woff2" as="font" type="font/woff2" crossorigin >

La déclaration font-display: swap dans votre @font-face dit au navigateur d'afficher immédiatement avec la police de repli et de basculer vers la police variable à son arrivée. Une police variable est un seul fichier avec une infinité de graisses, et une mauvaise stratégie de chargement la chargera deux fois pour rien si vous mélangez des déclarations avec et sans preload entre plusieurs feuilles de style. Un preload par fichier de subset. C'est toute la règle.


Vous voulez des polices variables livrées à moitié poids et qui ne font jamais sauter la page ? Engagez Brainy et nous livrerons votre pile typographique de production avec les bons axes, les bons subsets et la recette de chargement qui garde Lighthouse au vert.


Concept voxel illustrant la séquence de chargement d'une police variable : preload dans le head, font-display swap dans le CSS et une police de repli aux métriques ajustées qui évite le décalage de mise en page
Concept voxel illustrant la séquence de chargement d'une police variable : preload dans le head, font-display swap dans le CSS et une police de repli aux métriques ajustées qui évite le décalage de mise en page

Polices de repli aux métriques ajustées

Une police système de repli aux métriques ajustées utilise size-adjust, ascent-override et descent-override pour s'afficher avec le même rythme que la police variable. Sans cela, font-display: swap échange le texte invisible contre un décalage de mise en page à l'arrivée de la police variable. Avec l'ajustement des métriques, le basculement est visuellement imperceptible.

css
@font-face { font-family: 'YourFont Fallback'; src: local('Arial'); ascent-override: 90%; descent-override: 22%; line-gap-override: 0%; size-adjust: 107%; }

Les valeurs exactes dépendent de votre police variable. Des outils comme Fontaine et next/font génèrent ces valeurs automatiquement. Si vous construisez sur Next.js, next/font gère toute la chaîne de repli. Sinon, Fontaine est le chemin le plus rapide vers des valeurs d'override correctes sans les calculer à la main.

Quatre motifs d'animation que débloquent les polices variables

Hover-weight, scroll-weight, hover-slant et reveal-width. Ces quatre animations ne sont possibles qu'avec des polices variables, et elles méritent leur poids en fichier quand on les utilise avec parcimonie. Collez les quatre sur une même page et ça se lit comme une démo technique, pas comme un design.

Hover-weight fait passer font-weight de 400 à 700 au survol. L'interpolation est fluide parce que les données d'axe sont dans la police. transition: font-weight 0.15s ease sur l'élément, c'est tout le CSS. Ça marche sur les liens de navigation, les textes d'appel à l'action et les labels interactifs.

Scroll-weight pilote font-weight à partir de la position de défilement, grâce à un écouteur de scroll JavaScript qui mappe scrollY sur une valeur de la plage de l'axe. Gardez la plage serrée : de 300 à 700 se lit comme voulu, de 100 à 900 se lit comme un bug. Il a sa place dans les sections hero, où le texte s'alourdit à mesure que l'utilisateur défile vers le contenu.

Hover-slant fait passer font-style: oblique 0deg à font-style: oblique -10deg au survol. C'est l'alternative plus subtile à hover-weight, et elle paraît plus raffinée sur les liens de navigation et les éléments interactifs en ligne. Ne la combinez avec hover-weight que si le caractère a été conçu pour supporter les deux simultanément.

Reveal-width anime font-stretch dans le cadre d'une animation d'entrée. Le texte entre à font-stretch: 75% et s'étend jusqu'à font-stretch: 100% en 400ms. Combiné à un fondu, c'est l'un des motifs de révélation typographique les plus propres qui existent. Oubliez-le sur le texte courant. Il appartient uniquement au titrage et aux titres hero.

Sept sites qui livrent des polices variables à grande échelle

GitHub, Stripe Press, Vercel, Linear, The New York Times, Apple Music et Figma livrent chacun des polices variables en production, et les motifs qu'ils partagent sont ceux qui valent d'être copiés.

GitHub livre Mona Sans et Hubot Sans, deux polices variables sur mesure conçues pour la marque GitHub. Axe de graisse uniquement dans la plupart des contextes d'interface. Pas d'animation, sauf dans quelques sections marketing. Les poids de fichiers sont serrés parce qu'ils subsettent agressivement par groupe de langues.

Stripe Press utilise une police variable de titrage sur mesure pour ses titres éditoriaux. Ils poussent fort l'axe de graisse, avec des titres en graisses lourdes et des légendes en graisses légères depuis un seul fichier. Le corps optique est visible aux plus grandes tailles de titre. C'est l'usage de police variable le plus ambitieux éditorialement de cette liste.

Vercel livre Geist, sa propre police variable, déclinée en Geist Sans et Geist Mono. L'axe de graisse couvre toute la plage. La police est open source, le subsetting latin est propre, et elle se charge assez vite pour servir de référence de ce à quoi ressemble une police variable bien livrée dans une app Next.js en production.

Linear livre Inter en police variable. L'axe de graisse couvre les graisses d'interface, en restant dans la plage de graisses optiques plus étroite qui convient à une UI dense. C'est l'usage le plus conservateur de cette liste, et aussi l'un des plus justes pour une interface produit riche en données où l'animation ne ferait que du bruit.

The New York Times utilise une serif variable sur mesure pour le titrage. Les axes de graisse et de corps optique sont tous deux actifs, et les transitions entre titrage et texte courant utilisent l'axe de corps optique pour faire évoluer le rendu des caractères. C'est ce que l'usage de polices variables en production fait de plus sophistiqué à l'échelle éditoriale.

Apple Music livre San Francisco dans sa forme variable avec les axes de graisse et de corps optique actifs. Les transitions d'axe entre grande et petite taille sont assez fluides pour que la plupart des utilisateurs ne remarquent pas que la police est variable, ce qui est le bon objectif. La police doit disparaître dans l'expérience de lecture.

Figma livre Inter Display en police variable dans toute l'application. Si vous faites de la sélection de police de titrage pour une interface produit, étudiez comment Figma équilibre Inter aux petites graisses d'UI et Inter Display aux grandes tailles de titre. La discipline sur la plage de graisses mérite d'être copiée telle quelle.

La checklist de police variable avant déploiement

Passez-la en revue avant de livrer une police variable et vous attraperez les bugs qui mordent en production.

  • Subsetting vérifié. Lancez pyftsubset avec vos plages unicode cibles. Confirmez que le fichier de subset latin fait moins de 200 Ko. Vérifiez par échantillonnage que les caractères utilisés par votre contenu sont présents dans la sortie.
  • Axes documentés. Sachez quels axes la police livre et la plage min et max de chacun. Ne déclarez pas font-weight: 800 si l'axe plafonne à 700. Il plafonnera en silence et votre design sera faux.
  • Preload en place. Un <link rel="preload"> par fichier de subset, dans le <head>, avec l'attribut crossorigin, pointant uniquement vers le fichier latin principal. Précharger un fichier que vous n'utilisez pas gaspille de la bande passante.
  • font-display configuré. Chaque bloc @font-face a font-display: swap. Confirmez dans l'onglet Network des DevTools que les polices ne bloquent le rendu d'aucune ressource du chemin critique.
  • Métriques de repli ajustées. Un bloc @font-face pour la police système de repli avec size-adjust, ascent-override et descent-override calculés. Vérifiez en bridant à Slow 3G dans les DevTools et en cherchant un décalage de mise en page au chargement. Le CLS doit être à 0 ou proche de 0.
  • Axes utilisés par le design confirmés. Chaque axe déclaré dans le CSS est réellement utilisé dans le design final. Les axes présents dans le fichier de police mais absents du CSS ajoutent quand même des octets. Si vous n'utilisez pas la largeur, envisagez un subsetting de plage d'axe pour rogner ces octets.
  • Score Lighthouse vérifié. Lancez Lighthouse sur l'URL de production avec la police en place. Le LCP et le CLS liés à la police doivent être au vert. Sinon, les métriques de repli sont fausses ou le preload manque.
  • En-têtes CORS confirmés. Les polices variables chargées depuis un CDN ou une origine externe ont besoin des en-têtes Access-Control-Allow-Origin. Cherchez des erreurs CORS dans l'onglet Network. Un échec CORS produit un repli silencieux de la police, difficile à diagnostiquer en production.

FAQ

Qu'est-ce qu'une police variable et en quoi diffère-t-elle d'une police statique ?

Une police variable est un fichier de police unique qui contient toute la plage de dessin d'un caractère, contrôlée par des axes comme la graisse, la largeur, l'inclinaison et le corps optique. Une police statique est un instantané unique à une graisse et un style donnés. La différence pratique, c'est qu'une police variable vous permet d'utiliser font-weight: 350 ou d'animer la graisse en douceur en CSS, alors que les polices statiques n'autorisent que les valeurs spécifiques que la fonderie a choisi de livrer sous forme de fichiers séparés.

Les polices variables améliorent-elles vraiment les performances ?

Ça dépend du nombre de graisses que vous chargez. Si vous livrez deux graisses statiques totalisant 60 Ko, passer à une police variable de 180 Ko est une régression de performance. Si vous livrez cinq graisses totalisant 300 Ko, passer à une police variable subsettée de 120 Ko est un net gain. L'argument de performance en faveur des polices variables est réel, mais il ne s'applique qu'une fois le seuil des trois graisses franchi et le subsetting correctement fait.

Quelle est la meilleure police variable pour le design d'interface ?

Inter est la police variable la plus déployée en design d'interface. Son axe de graisse est bien calibré pour l'écran, elle subsette proprement, et Figma la livre comme référence de production. Roboto Flex est le bon choix quand vous avez besoin d'une large plage d'axes pour des mises en page éditoriales responsives. Recursive mérite l'étude si vous voulez à la fois une sans-serif variable et une mono variable issues d'une même famille. Pour des principes de design web qui appellent une face de titrage expressive, Fraunces et Playfair Display livrent tous deux de solides axes de corps optique et de graisse.

Comment utiliser font-variation-settings en CSS ?

font-variation-settings est la propriété bas niveau pour régler directement les axes d'une police variable, à l'aide de la balise d'axe à quatre lettres et d'une valeur numérique : font-variation-settings: 'wght' 450, 'wdth' 85. Pour les axes enregistrés comme la graisse et la largeur, privilégiez les propriétés CSS de haut niveau (font-weight, font-stretch) car elles héritent et se composent correctement. N'utilisez font-variation-settings que pour les axes personnalisés qui n'ont pas de propriété CSS de haut niveau.

Les polices variables peuvent-elles provoquer un décalage de mise en page ?

Oui, et elles sont plus susceptibles d'en provoquer que les polices statiques parce que le fichier est plus lourd et met plus de temps à charger. La solution, c'est font-display: swap combiné à une police de repli aux métriques ajustées. Cette combinaison rend le basculement visuellement imperceptible. Sans la police de repli aux métriques ajustées, font-display: swap échange le texte invisible contre un saut de mise en page visible à l'arrivée de la police. Les deux problèmes se règlent ensemble ou pas du tout.

Les polices variables sont-elles prises en charge par tous les navigateurs ?

Les polices variables sont prises en charge par tous les navigateurs modernes : Chrome, Firefox, Safari et Edge. Le socle est assez solide pour que vous n'ayez pas besoin d'un repli en police statique pour la compatibilité navigateur. Le pourcentage d'utilisateurs sur un navigateur qui ne prend pas en charge les polices variables est inférieur à un pour cent pour la plupart des sites en production. Prévoyez un repli en police système pour ce cas limite, pas une pile de polices statiques.

Comment fonctionne le subsetting d'une police variable ?

Le subsetting retire du fichier de police les glyphes que votre contenu ne rendra jamais, à l'aide d'un outil comme pyftsubset de la bibliothèque Python fonttools. Vous spécifiez les plages de caractères unicode dont vous avez besoin, et l'outil supprime tout le reste. Une police variable complète peut livrer 3 000 glyphes. Un subset latin seul en garde environ 250. Le résultat est un rendu visuel identique pour du contenu latin, pour environ un tiers du poids du fichier. Consultez l'entrée du glossaire sur le subsetting pour la référence complète des commandes.

Le changement que les polices variables débloquent vraiment

Les polices variables ne sont pas une curiosité typographique. Ce sont un moyen de livrer plus d'expression avec moins d'octets, et les équipes qui les traitent comme un outillage de production sont celles qui font de meilleure typographie pour moins de coût réseau.

L'architecture de variables Figma qui anime les design systems modernes s'applique naturellement aux axes des polices variables. Un token font-weight dans Figma est une valeur d'axe dans la police. Associez un système de tokens bien structuré à une police variable correctement subsettée et vous obtenez des décisions de design qui se propagent du fichier de design à la production sans ambiguïté. Ajoutez un système de couleurs OKLCH sur les états de survol et vous construisez des interactions produit qui paraissent pensées plutôt qu'assemblées.

La barre pour les polices variables en production, ce n'est pas « avons-nous livré une police variable ». La barre, c'est : subsettée correctement, préchargée comme il faut, métriques de repli ajustées, et uniquement les axes que le design utilise. C'est le standard qui sépare les équipes qui font de la typographie de production de celles qui font du théâtre typographique.

Vous voulez des polices variables livrées à moitié poids et qui ne font jamais sauter la page ? Engagez Brainy et nous livrerons votre pile typographique de production avec les bons axes, les bons subsets et la recette de chargement qui garde Lighthouse au vert.

Want variable fonts that ship at half the bytes and never jank the page? Brainy ships production typography stacks with the right axes, the right subsets, and the loading recipe that keeps Lighthouse green.

Get Started

More from Brainy Papers

Keep reading