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

@adrienlcp/styles

v0.19.0

Published

A reset, a reduced-motion switch, default tokens, Sass mixins for self-hosted fonts, container queries, a breakpoint, visually hidden text, a skip link that clears the notch, a fluid size in rem, a spin animation, a PostCSS plugin that lists Arial's metri

Readme

@adrienlcp/styles

The styling floor every app of mine starts from: a reset, a reduced-motion switch, and the Sass mixins that CSS cannot write yet. Plain .css for what CSS can say, .sass only for what it cannot — a custom property is not allowed in a media query or in unicode-range, and CSS has no mixins.

pnpm add @adrienlcp/styles

Plain CSS

| File | Does | | --- | --- | | reset.css | Box sizing, zeroed margins and paddings, inherited fonts on controls, bare buttons that fire on the first tap (touch-action: manipulation), links in their parent's color with their underline kept, bare lists, balanced headings, pretty paragraphs, and interpolate-size: allow-keywords so a transition reaches height: auto (Chromium; elsewhere the size snaps as before), no tap highlight on mobile — every pressable then owes its own pressed style —, headings and paragraphs that break a long word rather than overflow, a textarea that resizes only vertically, anchors that scroll to --scroll-offset below the top (the height of a sticky header, 0px until set), and #root isolated so a z-index inside the app never climbs over an overlay portalled to body. It leaves out what an app decides: scrollbar-gutter, font-synthesis, field-sizing on a textarea, and smooth scrolling, which would also animate the router's scroll on every navigation. Inside @layer reset, except [hidden], kept hidden whatever display a component gives it | | reduced-motion.css | Collapses --transition-fast, --transition-base and --transition-slow to 0ms under prefers-reduced-motion: reduce, ends every keyframe animation at once, looping ones included, and stills view transitions, which React's <ViewTransition> starts whatever the preference. Unlayered, so it beats the tokens wherever they are defined. A scroll-driven animation moves only as the reader scrolls, and a zero duration pins it to its last frame: its element declares --reduced-motion-duration: auto, which reaches none of its descendants |

Import them once — from JavaScript, or from the global stylesheet in Sass:

@use '@adrienlcp/styles/reset.css'
@use '@adrienlcp/styles/reduced-motion.css'

reset.css declares a layer: set the order in the document head before any stylesheet loads, so the reset stays under every component rule — <style>@layer reset, tokens, base, components;</style>.

Sass

Resolved through the sass export condition, which Vite reads; with the Sass CLI, use pkg:@adrienlcp/styles/breakpoints and --pkg-importer=node.

containers

A component answers the room it is given, not the screen: the same card lays out the same way in a sidebar on a desktop and full width on a phone.

@use '@adrienlcp/styles/containers'

.card
  @include containers.container

  .card-body
    @include containers.container-wide(30rem)
      grid-template-columns: auto 1fr
  • container($name: null) sets container-type: inline-size, and container-name when given one.
  • container-wide($width, $name: null) is width >= $width on the nearest container, or on the one named; container-narrow is its exact complement.
  • An element queries an ancestor, never itself — the container wraps what changes.
  • A container no longer takes its width from its content: inside a flex row without a width, an auto grid track or an absolutely positioned box, it collapses to zero. Its parent gives it a width.
  • With no container above, neither mixin matches. Make body one in the global stylesheet, so an unwrapped component falls back to the page width; position: fixed stays on the viewport.
  • The width is Sass interpolated into @container, so pass a value, not a custom property — @container cannot read one either.

breakpoints

For what depends on the device rather than the room a component has: the page shell, a sidebar that turns into a drawer, an overlay pinned to the viewport.

@use '@adrienlcp/styles/breakpoints'

.shell
  @include breakpoints.wide
    grid-template-columns: 16rem 1fr

$wide-screen is 56.25rem — rem in a media query reads the browser's font size, so a user who raises it gets the narrow layout sooner. wide is width >= $wide-screen and narrow its exact complement. Another value: @use '@adrienlcp/styles/breakpoints' with ($wide-screen: 64rem); a container threshold that follows the shell takes the same value.

fonts

$latin and $latin-ext are the unicode ranges Google Fonts cuts a Latin face into; font-face declares one self-hosted woff2 file.

@use '@adrienlcp/styles/fonts'

@include fonts.font-face('Archivo', '/fonts/archivo-latin.woff2', fonts.$latin, $weight: 100 900, $stretch: 62% 125%)
@include fonts.font-face('Archivo', '/fonts/archivo-latin-ext.woff2', fonts.$latin-ext, $weight: 100 900, $stretch: 62% 125%)

$weight, $style (normal), $stretch (left out) and $display (optional) are optional. optional keeps the fallback face for the page's whole life when the font misses the first ~100 ms, so the font never swaps in under a reader and moves a line; it is cached for the next page. Its preload keeps the default priority: fetchpriority="low" makes it miss those 100 ms on purpose.

fallback-faces writes a fallback face per weight band of a variable font, for the font fontaine scales poorly: fontaine writes one face for the whole font, measured at its default instance, so a heavier weight wraps later in Arial than in the web font.

@include fonts.fallback-faces('Sofia Sans', (ascent: 0.9, descent: 0.3, cap-height: 0.655), 0.964556, (300 449: 1.0094, 450 649: 1.0058, 650 800: 1.0567), $trimmed-to-capitals: true)
  • $metrics is the web font's ascent, descent and cap-height in em, read from its hhea and OS/2 tables; $size-adjust the ratio fontaine computes for the file.
  • Each $widths entry maps a weight range to a width ratio: the band's Arial cut scaled by $size-adjust, over the web font at the weight that band's text is set in, both measured over the app's own text — (arial × size-adjust) / web font. The face is scaled by $size-adjust divided by that ratio, so it sets the text exactly as wide as the web font. A ratio above 1 means scaled Arial is still too wide. The measure-font bin of @adrienlcp/measure-font prints them.
  • $size-adjust only sets the scale the ratios are read on — it cancels out of every face. It defaults to 1: pass $widths by name then, fallback-faces('Onest', $metrics, $widths: (400: 0.9505)), with ratios measured by measure-font --size-adjust 1.
  • A band from $bold-from (650) up is drawn in Arial Bold, and its ratio is measured over Arial Bold: a face that declares its weights is never synthesised bolder.
  • $style: italic writes the faces for the italic files, drawn in Arial Italic and Arial Bold Italic and measured over them. Without them italic text in the fallback takes the upright face — slanted by synthesis at best, upright under font-synthesis: none — at upright widths. Any other style is a compile error.
  • $figures maps the same ranges to the digits' own ratio, or is one ratio for every band — measure-font --figures --figure-feature tnum --figure-feature lnum for tabular-nums lining-nums prints it once when every weight gives the same. Each band gets a second face for U+0030-0039 alone, written after it so it wins for the digits, on the ascent and descent of the band's letters. One ratio cannot cover both when the font's digits stand to Arial's otherwise than its letters do: tabular figures scaled on the letters can set 15 to 25 % off — Barlow Condensed's 14 % narrower, arbor's 24 % — and a score jumps when the web font lands. A range missing from $figures is a compile error. $figure-separators: true widens that face to ,, . and :, so a clock such as 0:02 sets as wide as the web font draws it.
  • $figures is a fallback-faces argument only. A family left to fontaine gets one face, scaled on its letters: move it to fallback-faces to give its digits their own. A monospace family needs none — its digits are as wide as its letters.
  • $stretch declares the faces' font-stretch — 70%, or a range like 62% 70% — for a font with a width axis. Arial has none, so a condensed title otherwise falls back to full-width Arial and wraps: write one call per width band the app sets, each measured with measure-font --axis wdth=70.
  • $trimmed-to-capitals moves ascent and descent by the gap between the two capital heights, their sum kept, so a title trimmed to its capitals starts at the same height in both faces.
  • The faces are named <family> fallback, as fontaine names its own: skip the family in fontaine's skipFontFaceGeneration, and list metricTwins() after it for Linux and Android.

A font token names the fallback face itself. fontaine appends '<family> fallback' to a font-family declaration it reads, never to a custom property: a token keeps the family alone, and the metric-matched face is never used. Write it in:

--font-display: 'Bricolage Grotesque', 'Bricolage Grotesque fallback', sans-serif

findFallbackFailures in the audit lists a token that skips it.

Static files, one per weight. fontaine writes one fallback face per @font-face, copying its weight and style, but sizes every one from the same metrics — the family's entry in capsize's metrics collection, the regular cut, for any Google font, else the file it reads — and draws every one in regular Arial. The 600 and 700 faces carry the regular cut's size-adjust over regular Arial, and a bold line wraps at another width than the font's. Skip the family in skipFontFaceGeneration and write fallback-faces with one band per file: measure-font measures each file at its own weight against that one size-adjust, and Arial Bold from $bold-from — lower it to 600 when a 600 file measures closer to Arial Bold.

tokens

The tokens every app names the same way, so an app sets values, not names. Include defaults first in the app's :root; what the app declares after it wins.

@use '@adrienlcp/styles/tokens'

@layer tokens
  :root
    @include tokens.defaults
    --gutter: var(--space-l)

| Token | Default | | --- | --- | | --stroke-hair, --stroke-thin, --stroke-bold | 1px, 1.5px, 2px | | --hairline, --hairline-strong | a --stroke-hair solid line in --rule, --rule-strong | | --inset-hairline | the same hairline as an inset box-shadow, which takes no room | | --outline-thick, --outline-offset | 2px, 3px. A whole number of pixels: Chromium draws an outline in whole device pixels, so a 2.5px ring is 2px on a 1x screen | | --ring, --ring-offset | an --outline-thick solid outline in --focus, drawn --outline-offset outside the box | | --underline-offset, --tracking-tight | 0.24em, -0.02em | | --icon-s, --icon-m, --icon-l | 1rem, 1.25rem, 1.5rem | | --target | 44px, the smallest touch target | | --control-height | a drawn control's height: 2.75rem, never below --target | | --transition-fast, --transition-base, --transition-slow | 150ms, 250ms, 400ms — what reduced-motion.css collapses | | --ease-out | cubic-bezier(0.16, 1, 0.3, 1) | | --measure | 34em. Never ch: a ch is the width of the font's zero, which the fallback face and the web font draw differently, so a column in ch rewraps when the web font replaces the fallback | | --gutter, --gutter-left, --gutter-right | 1rem; a page's sides padded by --gutter, or by the inset a landscape notch covers when it is wider. Set --gutter on :root, where the pair is computed | | --safe-area-top, --safe-area-right, --safe-area-bottom, --safe-area-left | what the notch, the corners and the home indicator cover under viewport-fit=cover, 0px elsewhere; the bottom reads Android's safe-area-max-inset-bottom first. Pad an edge with max(var(--space-m), var(--safe-area-bottom)) |

--rule, --rule-strong and --focus are the app's palette. Until it declares them, a line is currentColor mixed toward transparent and the ring is currentColor: never invisible.

accessibility

@use '@adrienlcp/styles/accessibility'

.icon-label
  @include accessibility.visually-hidden
  • visually-hidden hides from sight, not from a screen reader. The focus rings are @adrienlcp/react-aria's focus module.

The skip link

@use '@adrienlcp/styles/accessibility'

.skip-link
  @include accessibility.skip-link
  background: var(--ink)
  color: var(--paper)
  padding: 0 var(--space-s)

skip-link($top: var(--space-2xs), $left: var(--gutter-left), $z-index: 30) parks the link above the viewport, position: fixed, and slides it in on :focus-visible, so only the keyboard sees it. It shows at max($top, var(--safe-area-top)), below a notch, and is parked past the top by that offset too, or its bottom would show under the notch. While a view transition runs it is hidden: the transition's picture of the page would show it parked. It writes transition: translate; a link that also scales on press writes the whole list itself. The look — background, colour, type, padding, radius — is the app's.

sizes

Every text size and every spacing is rem, so it follows the font size the user chose and the zoom. fluid lets a display size or a section's spacing grow with the screen without leaving rem:

@use '@adrienlcp/styles/sizes'

@layer tokens
  :root
    --text-display: #{sizes.fluid(2rem, 3.5rem)}
    --space-section: #{sizes.fluid(3rem, 6rem)}
  • fluid($min, $max, $from: 20rem, $to: 80rem) returns clamp($min, <rem> + <vw>, $max): $min up to a $from-wide viewport, $max from $to, a straight line between. Every argument is rem, and $max stays within 2.5 times $min — past that, a 200 % zoom on a wide screen no longer doubles the size (WCAG 1.4.4). Anything else is a compile error.

motion

@use '@adrienlcp/styles/motion'

.spinner
  @include motion.spin(calc(var(--transition-slow) * 2))
  • spin($duration) turns the element a full circle every $duration, at a constant speed, forever: a spinner, a sync icon. It writes the individual rotate property, so a transform or a translate on the element still holds. Its motion-spin keyframes come out once per stylesheet, at the root, wherever the first include sits — a layer, a media query. Under prefers-reduced-motion: reduce, reduced-motion.css ends the turn at once and the element rests as its rules draw it.

TypeScript

contrast

A palette is checked where it is written, not by eye: a test lists the token pairs that meet on screen, and fails when one falls under its WCAG minimum in the light or the dark scheme.

import { readFileSync } from 'node:fs'

import { findContrastFailures, WCAG_AA } from '@adrienlcp/styles/contrast'
import { expect, it } from 'vitest'

const TOKENS = readFileSync(new URL('_tokens.sass', import.meta.url), 'utf8')

it('every ink reads on every surface, in both themes', () => {
  expect(
    findContrastFailures(TOKENS, [
      { foreground: '--ink-soft', background: '--ground', minimum: WCAG_AA.text },
      { foreground: '--focus', background: '--ground', minimum: WCAG_AA.nonText }
    ])
  ).toEqual([])
})
  • WCAG_AA is text (4.5), largeText and nonText (3) — controls, icons, focus rings.
  • The stylesheet is .css or indented .sass. A token is oklch() or hex, light-dark() of those, or var() of another token.
  • A failure is too-low, with the ratio and the schemes it fails in, or unreadable: a token undeclared, declared twice with different values, a reference loop, a colour it cannot read (computed, or another notation), or a translucent background — what shows through decides that contrast.
  • A translucent foreground is measured over its background. An oklch() outside sRGB is clipped, as an sRGB screen draws it.

metric-twins

fontaine scales a fallback face to the web font with size-adjust and the *-override descriptors, but names one local font in its src: local("Arial"). Linux has no Arial and Android only Roboto, so there the face fails to load, text paints in an unscaled system font and moves when the web font swaps in. metricTwins(), listed after fontaine, names the fonts drawn on the same metrics too:

import { metricTwins } from '@adrienlcp/styles/metric-twins'
import fontaine from 'fontaine/postcss'

export default defineConfig({
  css: {
    postcss: { plugins: [fontaine({ fallbacks: ['Arial'] }), metricTwins()] }
  }
})

| A face naming | Also names | | --- | --- | | Arial | Liberation Sans, Arimo, Roboto | | Arial Bold | Arial-BoldMT, Liberation Sans Bold, Arimo Bold, Roboto Bold | | Arial Italic | Arial-ItalicMT, Liberation Sans Italic, Arimo Italic, Roboto Italic | | Arial Bold Italic | Arial-BoldItalicMT, Liberation Sans Bold Italic, Arimo Bold Italic, Roboto Bold Italic | | Courier New | Liberation Mono, Cousine | | Courier New Bold | CourierNewPS-BoldMT, Liberation Mono Bold, Cousine Bold | | Times New Roman | Liberation Serif, Tinos | | Times New Roman Bold | TimesNewRomanPS-BoldMT, Liberation Serif Bold, Tinos Bold |

A bold or italic cut is listed by its own names: local() matches one face, by its full or PostScript name, never a family.

  • Only a src that is one local() inside @font-face is rewritten, its name matched as a browser matches it, ignoring case; a downloaded file or a list stays as written, so the plugin never widens a face twice.
  • metricTwins({ twins: { Helvetica: ['Helvetica', 'Arial'] } }) adds a font or replaces a default one.
  • The plugin is typed on what it reads, so an app needs no postcss dependency of its own: Vite brings it.

audit

The rules nobody should have to remember, checked in a test that reads every stylesheet and every piece of markup:

import { globSync, readFileSync } from 'node:fs'
import { join } from 'node:path'

import { REACT_ARIA_TOKENS } from '@adrienlcp/react-aria'
import {
  findFallbackBandFailures,
  findFallbackFailures,
  findFontAttributeFailures,
  findTokenFailures,
  findTypeLiterals,
  findUnitFailures,
  findUnnamedValues,
  findUnreadFontFaces,
  webFontFamilies
} from '@adrienlcp/styles/audit'
import { describe, expect, it } from 'vitest'

const SOURCE_FOLDER = join(import.meta.dirname, '../..')
const findSourceFiles = (pattern: string) => globSync(pattern, { cwd: SOURCE_FOLDER })
const readSourceFile = (path: string) => readFileSync(join(SOURCE_FOLDER, path), 'utf8')

const STYLESHEETS = findSourceFiles('**/*.{sass,css}')
const MARKUP = findSourceFiles('**/*.{tsx,svg}')
const STYLESHEET_SOURCES = STYLESHEETS.map(readSourceFile)
const SOURCES = findSourceFiles('**/*.{sass,css,ts,tsx}').map(readSourceFile)
const WEB_FONTS = webFontFamilies(STYLESHEET_SOURCES)

it('[audit] finds the stylesheets it audits', () => {
  expect(STYLESHEETS).not.toEqual([])
})

describe.each(STYLESHEETS)('%s', (path) => {
  const stylesheet = readSourceFile(path)

  it('[units] sizes text, spacing and boxes in rem', () => {
    expect(findUnitFailures(stylesheet)).toEqual([])
  })

  it.skipIf(path.endsWith('_typography.sass'))('[voice] takes its text voice from the typography mixins', () => {
    expect(findTypeLiterals(stylesheet)).toEqual([])
  })

  it('[names] takes its radii, durations, text sizes and insets from tokens', () => {
    expect(findUnnamedValues(stylesheet)).toEqual([])
  })

  it('[fonts] writes every face so the audit reads its family', () => {
    expect(findUnreadFontFaces(stylesheet)).toEqual([])
  })

  it('[fonts] names the fallback face after every web font in a font token', () => {
    expect(findFallbackFailures(stylesheet, WEB_FONTS)).toEqual([])
  })
})

describe.each(MARKUP)('%s', (path) => {
  it('[fonts] sets text in a font token, never a family by name', () => {
    expect(findFontAttributeFailures(readSourceFile(path), STYLESHEET_SOURCES)).toEqual([])
  })
})

it('[fonts] draws a fallback face for every weight and style it sets, and none it does not', () => {
  expect(findFallbackBandFailures(STYLESHEET_SOURCES)).toEqual([])
})

it('[tokens] reads only custom properties that exist, under their one shared name', () => {
  expect(findTokenFailures(SOURCES, { provided: REACT_ARIA_TOKENS })).toEqual([])
})

REACT_ARIA_TOKENS comes from @adrienlcp/react-aria; an app without react-aria leaves the options out. The folder is the test's own, never the working directory: a monorepo whose vitest runs from the root would otherwise audit nothing.

One tag per check, the same in this package's tests and in an app's, so pnpm test -- -t fonts runs every font check: [units] for findUnitFailures, [voice] for findTypeLiterals, [names] for findUnnamedValues, [fonts] for the five font checks, [tokens] for findTokenFailures, and [audit] for the test that guards the audit itself.

  • Comments are never read, in a stylesheet or a script; a // inside a string or a url() is no comment.
  • findUnitFailures reads font-size, font, margin*, padding*, the gaps, text-indent, width, height and their logical, min- and max- forms, inset*, top, right, bottom, left, translate, flex-basis, the grid tracks (grid-template-columns, grid-auto-rows, …), the translate*() functions of a transform, and every custom property. A failure is pixels — any non-zero px value there, clamp() bounds and calc() terms included — or viewport-without-rem: a text size driven by vw, vh, vmin or a container unit with no rem part, which zoom cannot enlarge. It also lists ch, a size, a grid track or a custom property in ch — the width of the font's zero, which moves when the web font replaces the fallback —, fractional-outline, an outline, outline-width or --outline-* width (the offset aside) that is not a whole number of pixels; a border and a --stroke-* may be fractional. Strokes, outlines, radii, shadows and the touch target stay in px: their properties and their --stroke-*, --outline-*, --radius-*, --shadow-* and --target tokens are not read for pixels. outline: var(--stroke-thin) passes: the token's own declaration is checked.
  • Pixels a script must match go through a --*-px custom property, which passes: a board cell whose size the script computes in pixels is declared --cell-px: 24px and read by the stylesheet (calc(var(--cell-px) * 8)) and by the script through getComputedStyle. The suffix says the pixels are wanted; any other px custom property is pixels.
  • A surface sized on the viewport — a scoreboard on a TV — derives its sizes from a named unit token (calc(var(--cu) * 4)), which passes.
  • findTypeLiterals lists every font-weight, line-height and letter-spacing written as a number or a keyword: a text voice is declared once, in a mixin, and a component includes it. inherit and var() pass.
  • findUnnamedValues lists a radius (radius), a transition or animation duration or delay (duration) or a font-size (text-size) written as a literal: a radius is a --radius-* token, a duration is written over --transition-*, which reduced-motion.css collapses, a text size is a --text-* step. 0 and 0s pass; a font size passes when it reads a var() — a fitted floor max(var(--text-s), 7cqi), a unit token calc(var(--cu) * 4) —, keeps the parent's size (1em, 100%) or is a keyword. It also lists a raw env(safe-area-inset-*) outside the --safe-area-* tokens, which carry its 0px fallback (safe-area), and a hand-written max() over --safe-area-left or --safe-area-right (gutter): a page's sides, and whatever sits against them, take --gutter-left and --gutter-right. A max() over the top or the bottom inset passes — a sticky bar or a bottom bar writes its own.
  • webFontFamilies reads the families the stylesheets self-host: every fonts.font-face and fonts.fallback-faces include, every @font-face with a url() source, and the family of a hand-written <family> fallback face. A family held in a variable the same file assigns once is read, and so is one passed to a face mixin of the app's own — a @mixin whose face takes its family as a parameter. A family loaded elsewhere — a @fontsource import — is added by hand.
  • findUnreadFontFaces lists a face whose family (unread-family) or $widths bands (unread-weights) the audit cannot read: built in an @each loop, interpolated, a variable assigned twice, a map from another module, a loop variable of an @each or @for around the face — even one a variable of the file shares the name of —, or a face mixin of the same file included with a family it cannot read. Every font check then misses that family and passes in silence, so this one fails instead: write the faces out, one include per file.
  • findFallbackFailures lists a custom property whose font stack names one of those web fonts without '<family> fallback' right after it (missing-fallback, with the family): fontaine appends its fallback face to a font-family declaration, never inside a custom property, so the token paints in an unscaled system font until the font arrives — for good under font-display: optional when it misses. A web font listed after a fallback face is a glyph backup the fallback always shadows, and passes.
  • findFallbackBandFailures takes every stylesheet and checks the bands fallback-faces draws — fontaine's own faces copy every @font-face, and are not read — against the weights the stylesheets set, a literal, a keyword or a token: uncovered-weight, a font-weight no band of the family set in the same block covers — with no family there, no band of any family —, which paints in a face scaled for another weight; uncovered-style, a family that serves italic files with no italic band, so italic text in the fallback takes the upright face — em sets italic whether a stylesheet does or not; unused-band, a band no weight falls in, 400 always counted. A family is reported in lower case.
  • findFontAttributeFailures reads a .tsx or an .svg and lists a font-family attribute, a fontFamily prop or style key, or an inline font-family: that names a family instead of a font token (font-attribute). SVG text takes var(--font-…): a family named there gets no fallback face, and one the page does not serve falls back to serif. The SVG is inlined (?react): an SVG shown through <img> reads neither the page's fonts nor its tokens. A var() fallback is a generic family or another font token: var(--font-letter, 'Georgia') names a family, and fails. Given the stylesheets, as in the example, a token must be declared there as a font stack — an undeclared var(--font-typo) paints in the browser's serif, and var(--font-size-m) holds no family. inherit passes; an expression — fontFamily={family} — is not read.
  • findTokenFailures takes every source — stylesheets and the scripts that set a property through style — and lists a name read through var() but declared nowhere, not shared and not provided (undeclared: a rename without an alias); a second size in a family the shared set holds in one (parallel: a --control-*, --outline-*, --ring-* or --target-* name whose suffix is a size step or a size word — --control-m, --control-touch, --outline-thin — or whose value is a bare length; a derived --target-reach or a colour --control-ink passes); and a second name for a shared value (alias: --timing: cubic-bezier(0.16, 1, 0.3, 1) is --ease-out), compared only on values with a space, a comma or a function. A name built by interpolation — --g#{$n}, --pawn-${n} — is not read. The names tokens.defaults declares, and the palette names it reads, pass undeclared.