In the browser, how do you load a web font from JavaScript and detect when it is ready to use — for example before drawing text into a canvas?
answer
- a set hanging off the document
- the argument is a shorthand, not a name
- a promise for when things settle
- canvas never triggers the download
- construct, load, then add
basics
~20 sUse the document.fonts font set: call document.fonts.load('1em Brand') to force a face to download and await the returned promise, or construct a FontFace, await its load() and add it to document.fonts. document.fonts.ready resolves when pending font work has settled.
solid answer
~40 s`document.fonts` is a `FontFaceSet`. Calling `document.fonts.load('1em Brand')` asks the browser to load whatever faces match that CSS font shorthand and returns a promise resolving to the matched `FontFace` objects — note the shorthand needs a size, so a bare family string throws. `document.fonts.check('1em Brand')` is the synchronous "is it available right now" test, and `document.fonts.ready` is a promise that settles once the document has no pending font loads or layout work depending on them. To add a face entirely from script, construct `new FontFace('Brand', 'url(/fonts/brand.woff2) format("woff2")')`, `await face.load()`, then `document.fonts.add(face)`. This matters for canvas because setting `ctx.font` to a family that has not loaded does not trigger a download — the text is silently drawn in a fallback, so you must load first and draw in the `then`.
code
javascript · 16 linesconst face = new FontFace(
'Brand',
'url(/fonts/brand-700.woff2) format("woff2")',
{ weight: '700', style: 'normal', display: 'swap' }
);
face.load()
.then((loaded) => {
document.fonts.add(loaded);
const ctx = document.querySelector('canvas').getContext('2d');
ctx.font = '700 32px Brand';
ctx.fillText('Ready', 16, 48);
})
.catch(() => {
// font failed to load; canvas keeps its default family
});go deeper
Know that document.fonts exists, that load() returns a promise, and that the string you pass needs a size in front of the family name.
Be ready to distinguish load, check and ready, and to explain constructing a FontFace, awaiting load() and calling document.fonts.add() to register it.
Show judgment about when to leave the declarative path at all: canvas and text measurement genuinely need the API, while script-driven swap control trades robustness for precision.
Own the boundary — a documented rule for when a team may script font loading, with the failure mode spelled out, so a broken bundle never silently costs the product its typography.
## Why an imperative API exists CSS font loading is declarative and lazy: a face downloads when style resolution finds an element that uses it. That covers ordinary text, but it leaves gaps. Canvas drawing does not participate in style resolution at all. Measuring text before it is painted needs the real metrics. And a team that wants to control the swap moment itself — paint the fallback, then apply the web font in one deliberate step — needs to know when the file has arrived. The `FontFaceSet` API on `document.fonts` covers all three. ## The set: load, check, ready `document.fonts.load(font, text)` takes a **CSS font shorthand string**, not a family name. The shorthand requires at least a size and a family, so `'1em Brand'` works and `'Brand'` throws a syntax error — the single most common mistake with this API. It returns a promise resolving to an array of the `FontFace` objects that matched and finished loading. The optional second argument is the text you intend to render; it lets the browser skip faces whose character coverage does not include those characters. ```js await document.fonts.load('700 1em Brand'); ctx.font = '700 32px Brand'; ctx.fillText('Ready', 16, 48); ``` `document.fonts.check('1em Brand')` answers synchronously whether the matching faces are already available, returning a boolean. It does *not* start a download. `document.fonts.ready` is a promise that resolves when the document has finished its pending font loading and the layout operations that depend on it. It is the right hook for "the page has settled typographically", and the wrong hook for "my specific face is loaded" — a face that nothing has requested yet is not pending, so `ready` can resolve while your font has never been fetched. The set is also iterable and emits `loading`, `loadingdone` and `loadingerror` events, and exposes a `status` of `'loading'` or `'loaded'`. ## Constructing a face in script When the face is not in any stylesheet, build it directly: ```js const face = new FontFace( 'Brand', 'url(/fonts/brand-700.woff2) format("woff2")', { weight: '700', style: 'normal', display: 'swap' } ); try { await face.load(); document.fonts.add(face); } catch (err) { // the file failed; the page stays on its fallback } ``` The first argument is the family name, the second is a `src`-style value string, and the third is a descriptor object that accepts the same descriptors as the CSS rule — `weight`, `style`, `stretch`, `unicodeRange`, `display`, `featureSettings`. `load()` resolves with the face itself once the bytes are in; `add()` makes it available to CSS and canvas from that point. Adding before loading is legal, but the face is then in a loading state and behaves like a CSS-declared one. ## The canvas trap A canvas context is not part of the document's style tree. Assigning `ctx.font = '32px Brand'` when `Brand` has never been loaded does not queue a download and does not error; the context silently falls back to a default family and your drawing looks wrong. Worse, it often *works* in development because a preceding DOM element already pulled the face in. The rule is to load explicitly and draw in the continuation: ```js document.fonts.load('32px Brand').then(() => draw()); ``` ## Controlling the swap yourself The other common use is a deliberate two-stage render. Declare the face so it does not apply immediately, wait for it, then add a class that switches the family: ```js document.fonts.load('1em Brand').then(() => { document.documentElement.classList.add('fonts-loaded'); }); ``` This buys precision — you know exactly when the change happens and can batch several faces so the page restyles once instead of three times. It costs you a script dependency: if the JavaScript fails or is slow, the web font never applies at all, whereas the CSS descriptor works with no script running. Most teams should reach for the descriptor first and use the API only where the declarative path genuinely cannot reach, such as canvas and text measurement. ## Error behaviour `face.load()` rejects on a network or parse failure, and `document.fonts.load()` rejects if the shorthand cannot be parsed. Rejections are easy to leave unhandled in a promise chain, and an unhandled one here means a silent typographic regression rather than a crash — worth an explicit catch and a log.
- Why does document.fonts.load('Brand') fail while document.fonts.load('1em Brand') works?The argument is parsed as a CSS `font` shorthand, and that shorthand requires at least a size and a family. A bare family name is not a valid shorthand, so the call throws a syntax error. Any size works for the lookup — `1em` is conventional because you are asking about availability, not about rendering at that size.
- When is document.fonts.ready the wrong thing to await?When you care about one specific face. `ready` resolves once there is no *pending* font work, and a face nothing has requested yet is not pending — so it can resolve before your font has even been fetched. Await `document.fonts.load(...)` for a specific face and reserve `ready` for "typography has settled".
- What is the downside of driving the swap from JavaScript instead of font-display?You add a script dependency to a rendering decision. If the bundle fails, is blocked, or runs late, the web font never applies, whereas the CSS descriptor works with no JavaScript at all. The gain is precision — you can batch several faces so the page restyles once — so it is worth it only where that matters.
saying these in an interview costs you the question
- Passes a bare family name to document.fonts.load
- Assumes setting ctx.font downloads the face
- Uses document.fonts.ready to await one specific font
- Thinks document.fonts.check starts a download
- Adds a FontFace to the set and assumes it is loaded