home ~ socials ~ projects ~ rss

Check if a Font is Available on a Web Page with JavaScript

November 2025

I'm working on a page that provides some font tools1. Part of the functionality requires knowing if a given font is available on the page or not. This is the function I'm using to check that.

JavaScript
async function isFontAvailable(fontName) {
  try {
    const base = `position:absolute;top:0;left:0;opacity:0;`;
    const checker = document.createElement("div");
    const s = new Set();
    checker.style = `${base}font-family:cursive;`;
    checker.innerHTML = self.crypto.randomUUID();
    await document.body.appendChild(checker);
    s.add(checker.getBoundingClientRect().width);
    checker.style = `${base}font-family:"${fontName}", cursive;`;
    await document.fonts.ready;
    s.add(checker.getBoundingClientRect().width);
    checker.remove();
    return { value: s.size === 2 };
  } catch (error) {
    return { error: error };
  }
}

Example

Here's an example using bitty for the display. isFontAvailable() is a stand-alone function. It doesn't require bitty. I'm just using it to make the output simpler.

Output

false: Verdana
false: Rubik 80s Fade
false: Intentionally Missing

HTML

<style>
@font-face {
  font-family: "Rubik 80s Fade";
  src: url("https://fonts.gstatic.com/s/rubik80sfade/v2/U9MF6dW37nLSmnwZXyoV-uPXUhHwkbL8IHcK.ttf");
}
</style>

<script>
  window.FontCheckClass = class {
    async fontCheck(_event, el) {
      const fontName = el.dataset.name;
      const result = await isFontAvailable(fontName);
      if (result.value !== undefined) {
        el.innerHTML = `${result.value}: ${fontName}`;
      } else {
        el.innerHTML = result.error;
      }
    }
  }
</script>

<bitty-4-0 data-connect="FontCheckClass" data-send="fontCheck">
  <div data-name="Verdana" data-receive="fontCheck"></div>
  <div data-name="Rubik 80s Fade" data-receive="fontCheck"></div>
  <div data-name="Intentionally Missing" data-receive="fontCheck"></div>
</bitty-4-0>

Notes

  • The process works by making a transparent div with a piece of text in it. The text is set to the browsers generic cursive font and the width is measured. The font is then changed to the name of the requested font with cursive as a fallback and measured again.

    If the font is available and loads the two width values have a very high probability of being different sizes. When a difference is detected the value of the return payload is true.

  • The measurement values on my machine go to seven significant digits. It's possible that the target font produces the exact same width as the generic. If that happens, the response value of the payload would be false even though the font is available.
  • I'm not aware of a way in which false positives are possible.
  • The text string is a randomly generated UUID consisting of 36 characters. That size helps encourage a difference between the measured values. It also means you could do multiple checks to reduce the chance of false negatives.
  • Other examples I've seen suggest using documents.fonts and FontFaceSet: check(). That doesn't work as expected because Nonexistent fonts return true.
  • The document.fonts.ready does come into play to make sure that any external fonts have been loaded before running the checks.
  • Another example I saw created a canvas to do the sizing. It does effectively the same thing but with a larger overhead.
  • The return value from the function is an object in one of three possible states.

    { value: true }
    { value: false }
    { error: "SOME ERROR MESSAGE" }

    It's used to checking for the value key. It it exists, the function completed successfully and return a specific response. If the error key is returned instead it means the function failed. The font may or may not be available.

Tests
Head's Up

These tests are dependent on the specific fonts being available. They'll produce false failures if they aren't.

Output

Verdana
true
false
FAILED
Rubik 80s Fade
true
false
FAILED
Intentionally Missing
false
false
PASSED

HTML

<script>
const t =  {
  result:
      `<div class="status-STATUS">FONT</div>
      <div class="status-STATUS">EXPECTED</div>
      <div class="status-STATUS">GOT</div>
      <div class="status-STATUS">STATUS</div>`
};

window.TestClass = class {
  #testList = [
    ["Verdana", true],
    ["Rubik 80s Fade", true],
    ["Intentionally Missing", false],
  ];

  async testResults(_event, el) {
    for (let i = 0; i < this.#testList.length; i += 1) {
      const data = this.#testList[i];
      const subs = [
        ["FONT", data[0]],
        ["EXPECTED", data[1]],
      ];
      const result = await isFontAvailable(data[0]);
      const got = result.value;
      subs.push(["GOT", got]);
      const status = data[1] === got ? "PASSED" : "FAILED";
      subs.push(["STATUS", status]);
      const output = this.api.makeElements(t.result, subs);
      el.appendChild(output);
    }
  }
}
</script>

<bitty-4-0 data-connect="TestClass" data-send="testResults">
  <div data-receive="testResults"></div>
</bitty-4-0>

Wrap-up

Solving this took several hours. It feels like one of those things that should be easier. Maybe one day it will be. For now, this does what I need.

-a

end of line

Endnotes

The function uses features listed as being available in all major browsers. I checked it in my versions of

- Chrome on Mac

- Chrome on Windows 10

- Edge on Window 10

- Firefox on Mac

- Firefox on Windows 10

- Safari on Mac

- Safari on iOS

References

The cursive generic font name has been around since very early versions of the major browsers.

Footnotes

A place to put some details about font normalization. Under construction at the time of this writing.

Share link:
https://www.alanwsmith.com/en/35/17/1f/zv/?check-if-a-font-is-available-on-a-web-page-with-javascript