Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save RichLewis007/45384ad7d26361b85d8acbd2127a48fe to your computer and use it in GitHub Desktop.
Save RichLewis007/45384ad7d26361b85d8acbd2127a48fe to your computer and use it in GitHub Desktop.
Web browser 'Bookmarklet' to find the font of any text on a web page, and readable source code.

Font Finder Bookmarklet and Source Code

A one-click bookmarklet that shows a floating tooltip with the font in use (stack + likely active face), size, weight, line-height, letter/word spacing, and color for whatever text you hover over on a web page.


✨ Features

  • Esc to quit
  • Hold ⌥ Option / Alt to freeze the panel on the current element; release to resume
  • Displays:
    • Element tag, ID, and class
    • Font stack (font-family)
    • Likely active font (via document.fonts.check())
    • Size, weight, style, line-height, letter/word spacing
    • Text and background colors

📥 How to Install

  1. Copy the Minified bookmarklet code shown later in this file.
  2. Create a new bookmark in your browser.
  3. Paste the code into the URL/location field.
  4. Name it e.g. Font Peek.
  5. Visit any page and click on the bookmark. Hover over text to see details.

To Use:

Click on the bookmarklet (in your browser bookmarks pane or bookmarks toolbar) then click on any text on a web page. A floating box will appear showing the name of the font being used for that text.

Esc to quit.
Hold ⌥ Option / Alt to freeze the panel on the current element; release to resume.


⚠️ Notes

Alternatives

  • Browser extensions:

    • WhatFont (Chrome) – hover over text, see font instantly
    • Fontanello (Firefox/Chrome) – right-click text to see font details
  • Online tools:

    • Fonts Ninja – browser extension with preview/download options
    • WhatTheFont – identify fonts from images/screenshots

“font-family” vs actual rendered font

The 'stack' of fonts displayed shows the intent of the web page author to show a certain font style; the browser uses the first installed face that supports the glyphs. The bookmarklet flags a likely active family with document.fonts.check(...). It’s accurate for most Latin text; mixed scripts or missing glyphs can still fall back per character.

CSP Limitations

Some sites use Content Security Policy that blocks inline scripts (bookmarklets). If this bookmarklet doesn’t work:

  • Paste the code into the browser's DevTools console (CSP doesn’t block that).
  • Install a browser extension like WhatFont (Chrome) or Fontanello (FireFox). Extensions get around CSP because they’re “trusted” by the browser.
  • Save as a userscript in Tampermonkey/Greasemonkey, which also runs in a trusted context.

📜 Minified Bookmarklet

Paste this as the bookmark URL following to the instructions above:

javascript:(()=>{if(window.__fontPeekCleanup){window.__fontPeekCleanup();return}const d=document,w=window;const box=d.createElement('div');Object.assign(box.style,{position:'fixed',zIndex:2147483647,top:'10px',right:'10px',maxWidth:'420px',fontFamily:'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',fontSize:'12px',lineHeight:'1.4',background:'rgba(20,20,24,.92)',color:'#eef',padding:'10px 12px',borderRadius:'10px',boxShadow:'0 6px 24px rgba(0,0,0,.35)',whiteSpace:'pre-wrap',pointerEvents:'none'});box.innerHTML='Font Peek… hover any text (Esc to exit)';d.body.appendChild(box);let frozen=false;const q=(s)=>s.replace(/["']/g,'').trim();const likelyFont=(fam)=>{try{if(!('fonts'in d))return null;const list=fam.split(',').map(q);for(const name of list){if(name&&d.fonts.check('16px '+(/[\s]/.test(name)?`"${name}"`:name)))return name}return null}catch(e){return null}};const fmtColor=(c)=>{try{const ctx=d.createElement('canvas').getContext('2d');ctx.fillStyle=c;return ctx.fillStyle}catch{ return c}};const brief=(text,max=140)=>text.length>max?text.slice(0,max-1)+'…':text;const onMove=(ev)=>{if(frozen)return;const el=ev.target;if(!(el&&el.nodeType===1))return;const cs=w.getComputedStyle(el);if(!cs)return;const fam=cs.fontFamily||'';const active=likelyFont(fam);const color=fmtColor(cs.color);const bg=fmtColor(cs.backgroundColor);const lh=cs.lineHeight;const ls=cs.letterSpacing;const ws=cs.wordSpacing;const fw=cs.fontWeight;const fs=cs.fontStyle;const sz=cs.fontSize;const tt=cs.textTransform;const ff=brief(fam,260);box.innerHTML=`Element: <${el.tagName.toLowerCase()}${el.id?`#${el.id}`:''}${el.className?'.'+Array.from(el.classList).join('.') : ''}>\nfont-family: ${ff}\nlikely active: ${active??'(unknown)'}\nfont-size: ${sz} | weight: ${fw} | style: ${fs}\nline-height: ${lh} | letter-spacing: ${ls} | word-spacing: ${ws}\ncolor: ${color} | bg: ${bg}\ntext-transform: ${tt}`};const onKey=(e)=>{if(e.key==='Escape'){cleanup()} if(e.key==='Alt'){}};const onDown=(e)=>{if(e.key==='Alt')frozen=true};const onUp=(e)=>{if(e.key==='Alt')frozen=false};const cleanup=()=>{w.removeEventListener('mousemove',onMove,true);w.removeEventListener('keydown',onKey,true);w.removeEventListener('keyup',onUp,true);w.removeEventListener('keydown',onDown,true);box.remove();delete w.__fontPeekCleanup};w.addEventListener('mousemove',onMove,true);w.addEventListener('keydown',onKey,true);w.addEventListener('keyup',onUp,true);w.addEventListener('keydown',onDown,true);w.__fontPeekCleanup=cleanup;})();


📝 Readable version of the source code If you want to audit or tweak it, you can use this formatted source. You don’t need this for the bookmark -— use the minified one above. This code has the same logic, it is just expanded for clarity.

(() => {
  if (window.__fontPeekCleanup) { window.__fontPeekCleanup(); return; }

  const d = document, w = window;

  // Floating info box
  const box = d.createElement('div');
  Object.assign(box.style, {
    position: 'fixed',
    zIndex: 2147483647,
    top: '10px',
    right: '10px',
    maxWidth: '420px',
    fontFamily:
      'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
    fontSize: '12px',
    lineHeight: '1.4',
    background: 'rgba(20,20,24,.92)',
    color: '#eef',
    padding: '10px 12px',
    borderRadius: '10px',
    boxShadow: '0 6px 24px rgba(0,0,0,.35)',
    whiteSpace: 'pre-wrap',
    pointerEvents: 'none'
  });
  box.innerHTML = 'Font Peek… hover any text (Esc to exit)';
  d.body.appendChild(box);

  let frozen = false;

  const stripQuotes = (s) => s.replace(/["']/g, '').trim();

  // Try to guess the actual active face using the FontFaceSet API
  const likelyFont = (family) => {
    try {
      if (!('fonts' in d)) return null;
      const list = family.split(',').map(stripQuotes);
      for (const name of list) {
        if (!name) continue;
        const probe = /[\s]/.test(name) ? `"${name}"` : name;
        if (d.fonts.check(`16px ${probe}`)) return name;
      }
      return null;
    } catch {
      return null;
    }
  };

  const cssColor = (c) => {
    try {
      const ctx = d.createElement('canvas').getContext('2d');
      ctx.fillStyle = c;
      return ctx.fillStyle;
    } catch {
      return c;
    }
  };

  const brief = (text, max = 260) =>
    text.length > max ? text.slice(0, max - 1) + '…' : text;

  const onMove = (ev) => {
    if (frozen) return;
    const el = ev.target;
    if (!(el && el.nodeType === 1)) return;

    const cs = w.getComputedStyle(el);
    if (!cs) return;

    const fam = cs.fontFamily || '';
    const active = likelyFont(fam);

    const color = cssColor(cs.color);
    const bg = cssColor(cs.backgroundColor);

    const info = [
      `Element: <${el.tagName.toLowerCase()}${el.id ? `#${el.id}` : ''}${el.className ? '.' + Array.from(el.classList).join('.') : ''}>`,
      `font-family: ${brief(fam)}`,
      `likely active: ${active ?? '(unknown)'}`,
      `font-size: ${cs.fontSize} | weight: ${cs.fontWeight} | style: ${cs.fontStyle}`,
      `line-height: ${cs.lineHeight} | letter-spacing: ${cs.letterSpacing} | word-spacing: ${cs.wordSpacing}`,
      `color: ${color} | bg: ${bg}`,
      `text-transform: ${cs.textTransform}`
    ].join('\n');

    box.innerHTML = info;
  };

  const onKey = (e) => {
    if (e.key === 'Escape') cleanup();
  };

  const onDown = (e) => {
    if (e.key === 'Alt') frozen = true;
  };
  const onUp = (e) => {
    if (e.key === 'Alt') frozen = false;
  };

  const cleanup = () => {
    w.removeEventListener('mousemove', onMove, true);
    w.removeEventListener('keydown', onKey, true);
    w.removeEventListener('keyup', onUp, true);
    w.removeEventListener('keydown', onDown, true);
    box.remove();
    delete w.__fontPeekCleanup;
  };

  w.addEventListener('mousemove', onMove, true);
  w.addEventListener('keydown', onKey, true);
  w.addEventListener('keyup', onUp, true);
  w.addEventListener('keydown', onDown, true);

  w.__fontPeekCleanup = cleanup;
})();

📄 License

MIT License

Copyright (c) 2025 Rich Lewis

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights  
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
copies of the Software, and to permit persons to whom the Software is  
furnished to do so, subject to the following conditions:  

The above copyright notice and this permission notice shall be included in all  
copies or substantial portions of the Software.  

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  
SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment