[{"data":1,"prerenderedAt":2781},["ShallowReactive",2],{"api-blog-how-to-detect-fonts-a-website-uses":3,"api-blog-related-how-to-detect-fonts-a-website-uses":838},{"id":4,"title":5,"author":6,"body":7,"category":806,"date":807,"dateModified":808,"description":809,"extension":810,"faqs":811,"image":808,"meta":824,"navigation":441,"path":825,"readingTime":215,"relatedSlugs":826,"seo":830,"stem":831,"tags":832,"__hash__":837},"apiBlog/api-blog/how-to-detect-fonts-a-website-uses.md","How to Detect and Extract the Fonts a Website Uses","Soraia",{"type":8,"value":9,"toc":791},"minimark",[10,24,27,32,283,286,290,293,370,377,381,392,396,425,557,572,576,660,664,671,709,727,731,742,746,751,754,758,764,768,780,784,787],[11,12,13,14,18,19,23],"p",{},"The fastest way to see a website's fonts is DevTools: select an element and check the ",[15,16,17],"strong",{},"rendered fonts"," panel at the bottom of the Computed tab. It names what actually painted, which - and this is the whole subject of this post - is frequently not what the CSS asked for. ",[20,21,22],"code",{},"font-family"," is a wish list. The browser walks it until something works, and with lazy loading in the mix, what renders can differ from what is declared, per element, per moment.",[11,25,26],{},"For anything beyond one element, you want a script. Here are the three signals worth reading, and what each one can and cannot tell you.",[28,29,31],"h2",{"id":30},"signal-1-computed-styles-what-css-resolved-to","Signal 1: computed styles (what CSS resolved to)",[33,34,39],"pre",{"className":35,"code":36,"language":37,"meta":38,"style":38},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// Paste in the console: ranked font stacks in use\nObject.entries([...document.querySelectorAll('body *')].reduce((acc, el) => {\n  const f = getComputedStyle(el).fontFamily;\n  acc[f] = (acc[f] || 0) + 1;\n  return acc;\n}, {})).sort((a, b) => b[1] - a[1]).slice(0, 5)\n","js","",[20,40,41,50,123,154,201,213],{"__ignoreMap":38},[42,43,46],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sHwdD","// Paste in the console: ranked font stacks in use\n",[42,51,53,57,61,65,68,71,74,76,79,82,85,89,91,94,96,99,101,103,107,110,113,116,120],{"class":44,"line":52},2,[42,54,56],{"class":55},"sTEyZ","Object",[42,58,60],{"class":59},"sMK4o",".",[42,62,64],{"class":63},"s2Zo4","entries",[42,66,67],{"class":55},"([",[42,69,70],{"class":59},"...",[42,72,73],{"class":55},"document",[42,75,60],{"class":59},[42,77,78],{"class":63},"querySelectorAll",[42,80,81],{"class":55},"(",[42,83,84],{"class":59},"'",[42,86,88],{"class":87},"sfazB","body *",[42,90,84],{"class":59},[42,92,93],{"class":55},")]",[42,95,60],{"class":59},[42,97,98],{"class":63},"reduce",[42,100,81],{"class":55},[42,102,81],{"class":59},[42,104,106],{"class":105},"sHdIc","acc",[42,108,109],{"class":59},",",[42,111,112],{"class":105}," el",[42,114,115],{"class":59},")",[42,117,119],{"class":118},"spNyl"," =>",[42,121,122],{"class":59}," {\n",[42,124,126,129,132,135,138,141,144,146,148,151],{"class":44,"line":125},3,[42,127,128],{"class":118},"  const",[42,130,131],{"class":55}," f",[42,133,134],{"class":59}," =",[42,136,137],{"class":63}," getComputedStyle",[42,139,81],{"class":140},"swJcz",[42,142,143],{"class":55},"el",[42,145,115],{"class":140},[42,147,60],{"class":59},[42,149,150],{"class":55},"fontFamily",[42,152,153],{"class":59},";\n",[42,155,157,160,163,166,169,172,175,177,179,181,183,186,190,193,196,199],{"class":44,"line":156},4,[42,158,159],{"class":55},"  acc",[42,161,162],{"class":140},"[",[42,164,165],{"class":55},"f",[42,167,168],{"class":140},"] ",[42,170,171],{"class":59},"=",[42,173,174],{"class":140}," (",[42,176,106],{"class":55},[42,178,162],{"class":140},[42,180,165],{"class":55},[42,182,168],{"class":140},[42,184,185],{"class":59},"||",[42,187,189],{"class":188},"sbssI"," 0",[42,191,192],{"class":140},") ",[42,194,195],{"class":59},"+",[42,197,198],{"class":188}," 1",[42,200,153],{"class":59},[42,202,204,208,211],{"class":44,"line":203},5,[42,205,207],{"class":206},"s7zQu","  return",[42,209,210],{"class":55}," acc",[42,212,153],{"class":59},[42,214,216,219,222,225,227,230,232,234,237,239,242,244,246,249,252,254,257,260,262,265,267,270,272,275,277,280],{"class":44,"line":215},6,[42,217,218],{"class":59},"},",[42,220,221],{"class":59}," {}",[42,223,224],{"class":55},"))",[42,226,60],{"class":59},[42,228,229],{"class":63},"sort",[42,231,81],{"class":55},[42,233,81],{"class":59},[42,235,236],{"class":105},"a",[42,238,109],{"class":59},[42,240,241],{"class":105}," b",[42,243,115],{"class":59},[42,245,119],{"class":118},[42,247,248],{"class":55}," b[",[42,250,251],{"class":188},"1",[42,253,168],{"class":55},[42,255,256],{"class":59},"-",[42,258,259],{"class":55}," a[",[42,261,251],{"class":188},[42,263,264],{"class":55},"])",[42,266,60],{"class":59},[42,268,269],{"class":63},"slice",[42,271,81],{"class":55},[42,273,274],{"class":188},"0",[42,276,109],{"class":59},[42,278,279],{"class":188}," 5",[42,281,282],{"class":55},")\n",[11,284,285],{},"This tells you which stacks the cascade assigned where - the heading family vs the body family falls straight out of the counts. What it does not tell you: whether the first font in the stack ever loaded.",[28,287,289],{"id":288},"signal-2-documentfonts-what-actually-loaded","Signal 2: document.fonts (what actually loaded)",[11,291,292],{},"The FontFace API is the receipt:",[33,294,296],{"className":35,"code":295,"language":37,"meta":38,"style":38},"[...document.fonts].map(f => `${f.family} ${f.weight} - ${f.status}`)\n// e.g. [\"Sohne 400 - loaded\", \"Sohne 700 - loaded\", \"SohneMono 400 - unloaded\"]\n",[20,297,298,365],{"__ignoreMap":38},[42,299,300,302,304,306,308,311,313,316,318,320,322,325,327,329,332,335,338,340,342,345,347,350,353,355,357,360,363],{"class":44,"line":45},[42,301,162],{"class":55},[42,303,70],{"class":59},[42,305,73],{"class":55},[42,307,60],{"class":59},[42,309,310],{"class":55},"fonts]",[42,312,60],{"class":59},[42,314,315],{"class":63},"map",[42,317,81],{"class":55},[42,319,165],{"class":105},[42,321,119],{"class":118},[42,323,324],{"class":59}," `${",[42,326,165],{"class":55},[42,328,60],{"class":59},[42,330,331],{"class":55},"family",[42,333,334],{"class":59},"}",[42,336,337],{"class":59}," ${",[42,339,165],{"class":55},[42,341,60],{"class":59},[42,343,344],{"class":55},"weight",[42,346,334],{"class":59},[42,348,349],{"class":87}," - ",[42,351,352],{"class":59},"${",[42,354,165],{"class":55},[42,356,60],{"class":59},[42,358,359],{"class":55},"status",[42,361,362],{"class":59},"}`",[42,364,282],{"class":55},[42,366,367],{"class":44,"line":52},[42,368,369],{"class":48},"// e.g. [\"Sohne 400 - loaded\", \"Sohne 700 - loaded\", \"SohneMono 400 - unloaded\"]\n",[11,371,372,373,376],{},"Every registered webfont with its weight and load status. An ",[20,374,375],{},"unloaded"," entry means the CSS declared it but nothing on the page triggered the download - a font the site owns but this page does not use.",[28,378,380],{"id":379},"signal-3-the-network-where-fonts-come-from","Signal 3: the network (where fonts come from)",[11,382,383,384,387,388,391],{},"Filter DevTools' Network tab by \"Font\" and you get provenance: ",[20,385,386],{},"fonts.gstatic.com"," means Google Fonts, ",[20,389,390],{},"use.typekit.net"," means Adobe, files off the site's own domain mean self-hosted (and usually a commercial license).",[28,393,395],{"id":394},"all-three-from-python","All three from Python",[33,397,401],{"className":398,"code":399,"language":400,"meta":38,"style":38},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pip install playwright && playwright install chromium\n","bash",[20,402,403],{"__ignoreMap":38},[42,404,405,409,412,415,418,420,422],{"class":44,"line":45},[42,406,408],{"class":407},"sBMFI","pip",[42,410,411],{"class":87}," install",[42,413,414],{"class":87}," playwright",[42,416,417],{"class":59}," &&",[42,419,414],{"class":407},[42,421,411],{"class":87},[42,423,424],{"class":87}," chromium\n",[33,426,430],{"className":427,"code":428,"language":429,"meta":38,"style":38},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    font_urls = []\n    page.on(\"response\", lambda r: font_urls.append(r.url)\n            if r.request.resource_type == \"font\" else None)\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    page.evaluate(\"document.fonts.ready\")\n\n    loaded = page.evaluate(\n        \"[...document.fonts].map(f => `${f.family} ${f.weight} ${f.status}`)\")\n    used = page.evaluate(\"\"\"\n      [...new Set([...document.querySelectorAll('h1,h2,h3,p,a,button')]\n        .map(el => getComputedStyle(el).fontFamily))]\n    \"\"\")\n    browser.close()\n\nprint(\"LOADED:\", loaded)\nprint(\"USED:\", used)\nprint(\"SERVED FROM:\", {u.split('/')[2] for u in font_urls})\n","python",[20,431,432,437,443,448,453,458,463,469,475,481,487,492,498,504,510,516,522,528,534,539,545,551],{"__ignoreMap":38},[42,433,434],{"class":44,"line":45},[42,435,436],{},"from playwright.sync_api import sync_playwright\n",[42,438,439],{"class":44,"line":52},[42,440,442],{"emptyLinePlaceholder":441},true,"\n",[42,444,445],{"class":44,"line":125},[42,446,447],{},"with sync_playwright() as p:\n",[42,449,450],{"class":44,"line":156},[42,451,452],{},"    browser = p.chromium.launch()\n",[42,454,455],{"class":44,"line":203},[42,456,457],{},"    page = browser.new_page()\n",[42,459,460],{"class":44,"line":215},[42,461,462],{},"    font_urls = []\n",[42,464,466],{"class":44,"line":465},7,[42,467,468],{},"    page.on(\"response\", lambda r: font_urls.append(r.url)\n",[42,470,472],{"class":44,"line":471},8,[42,473,474],{},"            if r.request.resource_type == \"font\" else None)\n",[42,476,478],{"class":44,"line":477},9,[42,479,480],{},"    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n",[42,482,484],{"class":44,"line":483},10,[42,485,486],{},"    page.evaluate(\"document.fonts.ready\")\n",[42,488,490],{"class":44,"line":489},11,[42,491,442],{"emptyLinePlaceholder":441},[42,493,495],{"class":44,"line":494},12,[42,496,497],{},"    loaded = page.evaluate(\n",[42,499,501],{"class":44,"line":500},13,[42,502,503],{},"        \"[...document.fonts].map(f => `${f.family} ${f.weight} ${f.status}`)\")\n",[42,505,507],{"class":44,"line":506},14,[42,508,509],{},"    used = page.evaluate(\"\"\"\n",[42,511,513],{"class":44,"line":512},15,[42,514,515],{},"      [...new Set([...document.querySelectorAll('h1,h2,h3,p,a,button')]\n",[42,517,519],{"class":44,"line":518},16,[42,520,521],{},"        .map(el => getComputedStyle(el).fontFamily))]\n",[42,523,525],{"class":44,"line":524},17,[42,526,527],{},"    \"\"\")\n",[42,529,531],{"class":44,"line":530},18,[42,532,533],{},"    browser.close()\n",[42,535,537],{"class":44,"line":536},19,[42,538,442],{"emptyLinePlaceholder":441},[42,540,542],{"class":44,"line":541},20,[42,543,544],{},"print(\"LOADED:\", loaded)\n",[42,546,548],{"class":44,"line":547},21,[42,549,550],{},"print(\"USED:\", used)\n",[42,552,554],{"class":44,"line":553},22,[42,555,556],{},"print(\"SERVED FROM:\", {u.split('/')[2] for u in font_urls})\n",[11,558,559,560,563,564,567,568,571],{},"The ",[20,561,562],{},"document.fonts.ready"," await matters. Skip it and a ",[20,565,566],{},"font-display: swap"," site reports its fallback stack, because you measured during the swap window. This is the single most common reason font detection scripts return ",[20,569,570],{},"Arial"," for sites that very much do not use Arial.",[28,573,575],{"id":574},"what-each-signal-can-tell-you","What each signal can tell you",[577,578,579,598],"table",{},[580,581,582],"thead",{},[583,584,585,589,592,595],"tr",{},[586,587,588],"th",{},"Question",[586,590,591],{},"Computed styles",[586,593,594],{},"document.fonts",[586,596,597],{},"Network",[599,600,601,615,626,638,649],"tbody",{},[583,602,603,607,610,613],{},[604,605,606],"td",{},"Which family is used for headings vs body",[604,608,609],{},"Yes",[604,611,612],{},"No",[604,614,612],{},[583,616,617,620,622,624],{},[604,618,619],{},"Did the webfont actually load",[604,621,612],{},[604,623,609],{},[604,625,609],{},[583,627,628,631,634,636],{},[604,629,630],{},"Weights the site ships",[604,632,633],{},"Partially",[604,635,609],{},[604,637,609],{},[583,639,640,643,645,647],{},[604,641,642],{},"Google Fonts vs self-hosted vs Adobe",[604,644,612],{},[604,646,612],{},[604,648,609],{},[583,650,651,654,656,658],{},[604,652,653],{},"Works without executing the page",[604,655,612],{},[604,657,612],{},[604,659,612],{},[28,661,663],{"id":662},"or-use-one-api-call","Or use one API call",[11,665,666,667,670],{},"Our fonts endpoint reads the declared font sources from the page's HTML and CSS - ",[20,668,669],{},"@font-face"," rules, Google Fonts links, preloads - and returns families, weights, and where they are served from:",[33,672,674],{"className":398,"code":673,"language":400,"meta":38,"style":38},"curl \"https://miromiro.app/api/v1/fonts?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[20,675,676,693],{"__ignoreMap":38},[42,677,678,681,684,687,690],{"class":44,"line":45},[42,679,680],{"class":407},"curl",[42,682,683],{"class":59}," \"",[42,685,686],{"class":87},"https://miromiro.app/api/v1/fonts?url=stripe.com",[42,688,689],{"class":59},"\"",[42,691,692],{"class":55}," \\\n",[42,694,695,698,700,703,706],{"class":44,"line":52},[42,696,697],{"class":87},"  -H",[42,699,683],{"class":59},[42,701,702],{"class":87},"Authorization: Bearer ",[42,704,705],{"class":55},"$MIROMIRO_API_KEY",[42,707,708],{"class":59},"\"\n",[11,710,711,712,716,717,721,722,726],{},"For the full typographic token set (sizes, weights, line heights, letter spacing alongside the families), use ",[236,713,715],{"href":714},"/api/docs/design-tokens","/v1/extract"," instead. There is also a ",[236,718,720],{"href":719},"/api/tools/font-extractor","free font extractor"," if you want to check one site right now with no key. ",[236,723,725],{"href":724},"/api/dashboard/keys","Free key",": 100 credits a month, no card.",[28,728,730],{"id":729},"summary","Summary",[11,732,733,735,736,738,739,741],{},[20,734,22],{}," declares intent; ",[20,737,594],{}," reports reality; the network names the source. Cross-reference at least the first two, always after ",[20,740,562],{},", and be suspicious of any script that confidently reports a system font on a design-forward site - it probably measured mid-swap.",[28,743,745],{"id":744},"frequently-asked-questions","Frequently asked questions",[747,748,750],"h3",{"id":749},"how-do-i-find-a-websites-font-without-an-extension","How do I find a website's font without an extension?",[11,752,753],{},"DevTools → Computed tab → rendered fonts panel. It names what painted, not what was requested.",[747,755,757],{"id":756},"why-does-font-family-show-a-font-that-isnt-rendering","Why does font-family show a font that isn't rendering?",[11,759,760,761,763],{},"Because it is a fallback list. With ",[20,762,566],{},", the fallback can render and stay whenever the webfont is slow or never loads.",[747,765,767],{"id":766},"how-do-i-detect-fonts-from-a-script","How do I detect fonts from a script?",[11,769,770,771,773,774,776,777,779],{},"Playwright, await ",[20,772,562],{},", then read ",[20,775,594],{}," and computed ",[20,778,150],{}," values together (code above).",[747,781,783],{"id":782},"can-i-use-a-font-i-found-on-a-website","Can I use a font I found on a website?",[11,785,786],{},"Identifying is fine, using needs a license. Google Fonts yes; a site's self-hosted commercial webfonts are licensed to that site, not to you.",[788,789,790],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":38,"searchDepth":52,"depth":52,"links":792},[793,794,795,796,797,798,799,800],{"id":30,"depth":52,"text":31},{"id":288,"depth":52,"text":289},{"id":379,"depth":52,"text":380},{"id":394,"depth":52,"text":395},{"id":574,"depth":52,"text":575},{"id":662,"depth":52,"text":663},{"id":729,"depth":52,"text":730},{"id":744,"depth":52,"text":745,"children":801},[802,803,804,805],{"id":749,"depth":125,"text":750},{"id":756,"depth":125,"text":757},{"id":766,"depth":125,"text":767},{"id":782,"depth":125,"text":783},"Tutorials","2026-07-18",null,"Three ways to detect which fonts a website really uses - computed styles, document.fonts, and network sniffing - and why font-family lies to you.","md",[812,815,818,821],{"question":813,"answer":814},"How do I find out what font a website uses without an extension?","Open DevTools, select the element, and look at the Computed tab's rendered fonts section at the bottom - it names the font actually painted, not just the CSS stack. Or run document.fonts in the console to list every FontFace the page registered and whether each loaded.",{"question":816,"answer":817},"Why does font-family show a font that isn't actually rendering?","font-family is a wish list, not a receipt. The browser walks the stack until something is available; with font-display: swap a fallback renders first and stays if the webfont never finishes loading. Only the rendered-fonts panel or document.fonts tells you what really painted.",{"question":819,"answer":820},"How can I detect fonts from a script instead of DevTools?","Load the page with Playwright, await document.fonts.ready, then read both document.fonts (registered FontFaces with load status) and getComputedStyle fontFamily values across elements. Cross-referencing the two separates fonts that loaded from fonts that were merely requested.",{"question":822,"answer":823},"Can I legally use a font I found on a website?","Identifying a font is fine; using it needs a license. Google Fonts are open, but commercial webfonts (the files served from the site's own domain or a foundry CDN) are licensed to that site, not to you. Buy or substitute.",{},"/api-blog/how-to-detect-fonts-a-website-uses",[827,828,829],"how-to-extract-website-color-palette-programmatically","how-to-extract-design-tokens-from-any-website-in-python","design-token-types-reference",{"title":5,"description":809},"api-blog/how-to-detect-fonts-a-website-uses",[833,834,835,429,836],"fonts","font detection","javascript","typography","j1PMCGDTmZ62w1LGv7I0rP5_XmM7utcCv420iy6QF7Q",[839,1500,2182],{"id":840,"title":841,"author":6,"body":842,"category":806,"date":807,"dateModified":808,"description":1476,"extension":810,"faqs":1477,"image":808,"meta":1489,"navigation":441,"path":1490,"readingTime":215,"relatedSlugs":1491,"seo":1493,"stem":1494,"tags":1495,"__hash__":1499},"apiBlog/api-blog/how-to-extract-website-color-palette-programmatically.md","How to Extract a Website's Color Palette Programmatically",{"type":8,"value":843,"toc":1462},[844,855,858,862,865,890,953,956,960,963,1059,1066,1070,1073,1268,1271,1275,1372,1374,1404,1416,1418,1421,1423,1427,1434,1438,1441,1445,1448,1452,1459],[11,845,846,847,854],{},"There are two fundamentally different ways to extract a color palette from a website in code: quantize the pixels of a screenshot, or read the colors out of the CSS. They give different answers, and one of them is usually wrong for what you want. The short version: ",[15,848,849,850,60],{},"pixel methods find what a page looks like; CSS methods find what the brand ",[851,852,853],"em",{},"is"," If you want brand colors, read the CSS.",[11,856,857],{},"Here are both, working, then the comparison.",[28,859,861],{"id":860},"method-1-quantize-a-screenshot-fast-misleading","Method 1: quantize a screenshot (fast, misleading)",[11,863,864],{},"The classic approach - screenshot the page, run color quantization:",[33,866,868],{"className":398,"code":867,"language":400,"meta":38,"style":38},"pip install playwright colorthief\nplaywright install chromium\n",[20,869,870,881],{"__ignoreMap":38},[42,871,872,874,876,878],{"class":44,"line":45},[42,873,408],{"class":407},[42,875,411],{"class":87},[42,877,414],{"class":87},[42,879,880],{"class":87}," colorthief\n",[42,882,883,886,888],{"class":44,"line":52},[42,884,885],{"class":407},"playwright",[42,887,411],{"class":87},[42,889,424],{"class":87},[33,891,893],{"className":427,"code":892,"language":429,"meta":38,"style":38},"from io import BytesIO\nfrom colorthief import ColorThief\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    shot = page.screenshot()\n    browser.close()\n\npalette = ColorThief(BytesIO(shot)).get_palette(color_count=6)\nprint(['#%02x%02x%02x' % c for c in palette])\n",[20,894,895,900,905,909,913,917,921,926,930,935,939,943,948],{"__ignoreMap":38},[42,896,897],{"class":44,"line":45},[42,898,899],{},"from io import BytesIO\n",[42,901,902],{"class":44,"line":52},[42,903,904],{},"from colorthief import ColorThief\n",[42,906,907],{"class":44,"line":125},[42,908,436],{},[42,910,911],{"class":44,"line":156},[42,912,442],{"emptyLinePlaceholder":441},[42,914,915],{"class":44,"line":203},[42,916,447],{},[42,918,919],{"class":44,"line":215},[42,920,452],{},[42,922,923],{"class":44,"line":465},[42,924,925],{},"    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n",[42,927,928],{"class":44,"line":471},[42,929,480],{},[42,931,932],{"class":44,"line":477},[42,933,934],{},"    shot = page.screenshot()\n",[42,936,937],{"class":44,"line":483},[42,938,533],{},[42,940,941],{"class":44,"line":489},[42,942,442],{"emptyLinePlaceholder":441},[42,944,945],{"class":44,"line":494},[42,946,947],{},"palette = ColorThief(BytesIO(shot)).get_palette(color_count=6)\n",[42,949,950],{"class":44,"line":500},[42,951,952],{},"print(['#%02x%02x%02x' % c for c in palette])\n",[11,954,955],{},"Works in ten lines. The problem is that a screenshot has no idea what is UI and what is content. A hero photo dominates the pixel math, anti-aliased text contributes hundreds of blend colors that exist nowhere in the stylesheet, and the accent color that defines the brand might be forty pixels of button. Use this method for artwork and photos; do not use it for brand palettes.",[28,957,959],{"id":958},"method-2-read-the-css-the-right-answer","Method 2: read the CSS (the right answer)",[11,961,962],{},"Ask the browser what colors the stylesheet actually applies:",[33,964,966],{"className":427,"code":965,"language":429,"meta":38,"style":38},"from collections import Counter\nfrom playwright.sync_api import sync_playwright\n\nCOLLECT = \"\"\"\n() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {\n  const s = getComputedStyle(el);\n  return [s.color, s.backgroundColor, s.borderTopColor];\n})\n\"\"\"\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    values = page.evaluate(COLLECT)\n    browser.close()\n\nskip = {\"rgba(0, 0, 0, 0)\", \"rgb(0, 0, 0)\", \"rgb(255, 255, 255)\"}\nfor color, count in Counter(v for v in values if v not in skip).most_common(8):\n    print(f\"{count:>5}  {color}\")\n",[20,967,968,973,977,981,986,991,996,1001,1006,1011,1015,1019,1023,1027,1031,1036,1040,1044,1049,1054],{"__ignoreMap":38},[42,969,970],{"class":44,"line":45},[42,971,972],{},"from collections import Counter\n",[42,974,975],{"class":44,"line":52},[42,976,436],{},[42,978,979],{"class":44,"line":125},[42,980,442],{"emptyLinePlaceholder":441},[42,982,983],{"class":44,"line":156},[42,984,985],{},"COLLECT = \"\"\"\n",[42,987,988],{"class":44,"line":203},[42,989,990],{},"() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {\n",[42,992,993],{"class":44,"line":215},[42,994,995],{},"  const s = getComputedStyle(el);\n",[42,997,998],{"class":44,"line":465},[42,999,1000],{},"  return [s.color, s.backgroundColor, s.borderTopColor];\n",[42,1002,1003],{"class":44,"line":471},[42,1004,1005],{},"})\n",[42,1007,1008],{"class":44,"line":477},[42,1009,1010],{},"\"\"\"\n",[42,1012,1013],{"class":44,"line":483},[42,1014,442],{"emptyLinePlaceholder":441},[42,1016,1017],{"class":44,"line":489},[42,1018,447],{},[42,1020,1021],{"class":44,"line":494},[42,1022,452],{},[42,1024,1025],{"class":44,"line":500},[42,1026,457],{},[42,1028,1029],{"class":44,"line":506},[42,1030,480],{},[42,1032,1033],{"class":44,"line":512},[42,1034,1035],{},"    values = page.evaluate(COLLECT)\n",[42,1037,1038],{"class":44,"line":518},[42,1039,533],{},[42,1041,1042],{"class":44,"line":524},[42,1043,442],{"emptyLinePlaceholder":441},[42,1045,1046],{"class":44,"line":530},[42,1047,1048],{},"skip = {\"rgba(0, 0, 0, 0)\", \"rgb(0, 0, 0)\", \"rgb(255, 255, 255)\"}\n",[42,1050,1051],{"class":44,"line":536},[42,1052,1053],{},"for color, count in Counter(v for v in values if v not in skip).most_common(8):\n",[42,1055,1056],{"class":44,"line":541},[42,1057,1058],{},"    print(f\"{count:>5}  {color}\")\n",[11,1060,1061,1062,60],{},"Frequency ranking does the rest: the most-used saturated color is almost always the brand primary. For the full version with fonts, spacing, and the cleanup steps, see the ",[236,1063,1065],{"href":1064},"/api/blog/how-to-extract-design-tokens-from-any-website-in-python","design tokens guide",[28,1067,1069],{"id":1068},"checking-one-site-by-hand-use-the-console","Checking one site by hand? Use the console",[11,1071,1072],{},"Paste this in DevTools on any page:",[33,1074,1076],{"className":35,"code":1075,"language":37,"meta":38,"style":38},"Object.entries([...document.querySelectorAll('body *')].reduce((acc, el) => {\n  const c = getComputedStyle(el).backgroundColor;\n  if (c !== 'rgba(0, 0, 0, 0)') acc[c] = (acc[c] || 0) + 1;\n  return acc;\n}, {})).sort((a, b) => b[1] - a[1]).slice(0, 8)\n",[20,1077,1078,1126,1150,1205,1213],{"__ignoreMap":38},[42,1079,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124],{"class":44,"line":45},[42,1081,56],{"class":55},[42,1083,60],{"class":59},[42,1085,64],{"class":63},[42,1087,67],{"class":55},[42,1089,70],{"class":59},[42,1091,73],{"class":55},[42,1093,60],{"class":59},[42,1095,78],{"class":63},[42,1097,81],{"class":55},[42,1099,84],{"class":59},[42,1101,88],{"class":87},[42,1103,84],{"class":59},[42,1105,93],{"class":55},[42,1107,60],{"class":59},[42,1109,98],{"class":63},[42,1111,81],{"class":55},[42,1113,81],{"class":59},[42,1115,106],{"class":105},[42,1117,109],{"class":59},[42,1119,112],{"class":105},[42,1121,115],{"class":59},[42,1123,119],{"class":118},[42,1125,122],{"class":59},[42,1127,1128,1130,1133,1135,1137,1139,1141,1143,1145,1148],{"class":44,"line":52},[42,1129,128],{"class":118},[42,1131,1132],{"class":55}," c",[42,1134,134],{"class":59},[42,1136,137],{"class":63},[42,1138,81],{"class":140},[42,1140,143],{"class":55},[42,1142,115],{"class":140},[42,1144,60],{"class":59},[42,1146,1147],{"class":55},"backgroundColor",[42,1149,153],{"class":59},[42,1151,1152,1155,1157,1160,1163,1166,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203],{"class":44,"line":125},[42,1153,1154],{"class":206},"  if",[42,1156,174],{"class":140},[42,1158,1159],{"class":55},"c",[42,1161,1162],{"class":59}," !==",[42,1164,1165],{"class":59}," '",[42,1167,1168],{"class":87},"rgba(0, 0, 0, 0)",[42,1170,84],{"class":59},[42,1172,192],{"class":140},[42,1174,106],{"class":55},[42,1176,162],{"class":140},[42,1178,1159],{"class":55},[42,1180,168],{"class":140},[42,1182,171],{"class":59},[42,1184,174],{"class":140},[42,1186,106],{"class":55},[42,1188,162],{"class":140},[42,1190,1159],{"class":55},[42,1192,168],{"class":140},[42,1194,185],{"class":59},[42,1196,189],{"class":188},[42,1198,192],{"class":140},[42,1200,195],{"class":59},[42,1202,198],{"class":188},[42,1204,153],{"class":59},[42,1206,1207,1209,1211],{"class":44,"line":156},[42,1208,207],{"class":206},[42,1210,210],{"class":55},[42,1212,153],{"class":59},[42,1214,1215,1217,1219,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249,1251,1253,1255,1257,1259,1261,1263,1266],{"class":44,"line":203},[42,1216,218],{"class":59},[42,1218,221],{"class":59},[42,1220,224],{"class":55},[42,1222,60],{"class":59},[42,1224,229],{"class":63},[42,1226,81],{"class":55},[42,1228,81],{"class":59},[42,1230,236],{"class":105},[42,1232,109],{"class":59},[42,1234,241],{"class":105},[42,1236,115],{"class":59},[42,1238,119],{"class":118},[42,1240,248],{"class":55},[42,1242,251],{"class":188},[42,1244,168],{"class":55},[42,1246,256],{"class":59},[42,1248,259],{"class":55},[42,1250,251],{"class":188},[42,1252,264],{"class":55},[42,1254,60],{"class":59},[42,1256,269],{"class":63},[42,1258,81],{"class":55},[42,1260,274],{"class":188},[42,1262,109],{"class":59},[42,1264,1265],{"class":188}," 8",[42,1267,282],{"class":55},[11,1269,1270],{},"Fastest palette check that exists. It just does not scale past the tab you are in.",[28,1272,1274],{"id":1273},"screenshot-vs-css-vs-api","Screenshot vs CSS vs API",[577,1276,1277,1292],{},[580,1278,1279],{},[583,1280,1281,1283,1286,1289],{},[586,1282],{},[586,1284,1285],{},"Screenshot quantization",[586,1287,1288],{},"CSS computed styles",[586,1290,1291],{},"MiroMiro API",[599,1293,1294,1306,1318,1329,1347,1361],{},[583,1295,1296,1299,1302,1304],{},[604,1297,1298],{},"Finds brand accent colors",[604,1300,1301],{},"Poorly (pixel-count bias)",[604,1303,609],{},[604,1305,609],{},[583,1307,1308,1311,1314,1316],{},[604,1309,1310],{},"Affected by photos in the page",[604,1312,1313],{},"Badly",[604,1315,612],{},[604,1317,612],{},[583,1319,1320,1323,1325,1327],{},[604,1321,1322],{},"Invents colors (anti-aliasing)",[604,1324,609],{},[604,1326,612],{},[604,1328,612],{},[583,1330,1331,1334,1337,1344],{},[604,1332,1333],{},"Gradients",[604,1335,1336],{},"Sort of (as pixels)",[604,1338,1339,1340,1343],{},"Needs ",[20,1341,1342],{},"backgroundImage"," too",[604,1345,1346],{},"Yes, as gradient strings",[583,1348,1349,1352,1355,1358],{},[604,1350,1351],{},"Output format",[604,1353,1354],{},"RGB tuples",[604,1356,1357],{},"rgb() strings",[604,1359,1360],{},"Hex, ranked, plus the rest of the token set",[583,1362,1363,1366,1368,1370],{},[604,1364,1365],{},"Runs without your own browser infra",[604,1367,612],{},[604,1369,612],{},[604,1371,609],{},[28,1373,663],{"id":662},[33,1375,1377],{"className":398,"code":1376,"language":400,"meta":38,"style":38},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[20,1378,1379,1392],{"__ignoreMap":38},[42,1380,1381,1383,1385,1388,1390],{"class":44,"line":45},[42,1382,680],{"class":407},[42,1384,683],{"class":59},[42,1386,1387],{"class":87},"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients",[42,1389,689],{"class":59},[42,1391,692],{"class":55},[42,1393,1394,1396,1398,1400,1402],{"class":44,"line":52},[42,1395,697],{"class":87},[42,1397,683],{"class":59},[42,1399,702],{"class":87},[42,1401,705],{"class":55},[42,1403,708],{"class":59},[11,1405,1406,1407,1411,1412,1415],{},"Ranked palette with gradients included, normalized, 10 credits a call. There is also a ",[236,1408,1410],{"href":1409},"/api/tools/color-palette-extractor","free color palette extractor"," that runs the same engine in the browser if you want to eyeball a site before writing any code. ",[236,1413,1414],{"href":724},"Free key here"," - 100 credits a month, no card.",[28,1417,730],{"id":729},[11,1419,1420],{},"Read the CSS, not the pixels. Screenshot quantization answers \"what does this image look like\", which is the wrong question for a brand palette, and it will confidently hand you the colors of a stock photo. The console snippet for one site, Playwright for a script, the API when it needs to run inside something.",[28,1422,745],{"id":744},[747,1424,1426],{"id":1425},"how-do-i-get-a-websites-color-palette-in-python","How do I get a website's color palette in Python?",[11,1428,1429,1430,1433],{},"Playwright plus ",[20,1431,1432],{},"getComputedStyle"," counting (code above). Skip ColorThief-on-screenshot for brand work - it measures content, not identity.",[747,1435,1437],{"id":1436},"why-does-screenshot-extraction-give-wrong-brand-colors","Why does screenshot extraction give wrong brand colors?",[11,1439,1440],{},"Pixel counts reward photos and anti-aliasing blends, not the forty pixels of button that carry the actual accent color.",[747,1442,1444],{"id":1443},"can-i-do-this-in-the-browser-console","Can I do this in the browser console?",[11,1446,1447],{},"Yes - the snippet above ranks background colors on any page you have open.",[747,1449,1451],{"id":1450},"how-do-i-get-hex-instead-of-rgb-strings","How do I get hex instead of rgb() strings?",[11,1453,1454,1455,1458],{},"Three ",[20,1456,1457],{},"parseInt"," calls per channel, or use an API that normalizes for you.",[788,1460,1461],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":38,"searchDepth":52,"depth":52,"links":1463},[1464,1465,1466,1467,1468,1469,1470],{"id":860,"depth":52,"text":861},{"id":958,"depth":52,"text":959},{"id":1068,"depth":52,"text":1069},{"id":1273,"depth":52,"text":1274},{"id":662,"depth":52,"text":663},{"id":729,"depth":52,"text":730},{"id":744,"depth":52,"text":745,"children":1471},[1472,1473,1474,1475],{"id":1425,"depth":125,"text":1426},{"id":1436,"depth":125,"text":1437},{"id":1443,"depth":125,"text":1444},{"id":1450,"depth":125,"text":1451},"Two working ways to get a site's color palette in code - screenshot quantization vs reading the CSS - and which one returns the actual brand colors.",[1478,1480,1483,1486],{"question":1426,"answer":1479},"Two ways: quantize a screenshot with Pillow or ColorThief (fast, but photos in the page skew the result), or load the page with Playwright and count computed color and background-color values across elements (slower, but returns the actual CSS colors the brand uses). For brand palettes, use the CSS approach.",{"question":1481,"answer":1482},"Why does screenshot color extraction give wrong brand colors?","Because a screenshot has no idea what is UI and what is content. A hero photo of a sunset dominates the pixel counts, anti-aliasing invents hundreds of blend colors that exist nowhere in the CSS, and the true accent color of buttons and links can occupy very few pixels.",{"question":1484,"answer":1485},"Can I extract a color palette in the browser console?","Yes - a few lines of JavaScript over getComputedStyle gives you ranked colors for the page you are on. It is the fastest way to check one site by hand; it just is not automatable across many URLs without a headless browser around it.",{"question":1487,"answer":1488},"How do I get hex values instead of rgb() strings?","Computed styles always return rgb()/rgba(). Convert with three parseInt calls, or use an extraction API that normalizes to hex for you.",{},"/api-blog/how-to-extract-website-color-palette-programmatically",[828,1492,829],"how-to-detect-fonts-a-website-uses",{"title":841,"description":1476},"api-blog/how-to-extract-website-color-palette-programmatically",[1496,429,835,1497,1498],"color palette","colors","extraction","ZuBrzYUURU-j3lr-muPvdPr3xAzZrFGkvJ17zfbtBkg",{"id":1501,"title":1502,"author":6,"body":1503,"category":806,"date":807,"dateModified":808,"description":2160,"extension":810,"faqs":2161,"image":808,"meta":2171,"navigation":441,"path":2172,"readingTime":471,"relatedSlugs":2173,"seo":2175,"stem":2176,"tags":2177,"__hash__":2181},"apiBlog/api-blog/how-to-extract-design-tokens-from-any-website-in-python.md","How to Extract Design Tokens from Any Website in Python",{"type":8,"value":1504,"toc":2144},[1505,1512,1515,1519,1522,1544,1547,1715,1718,1726,1740,1744,1747,1785,1788,1792,1795,1835,1839,1842,1899,1903,1982,1989,1991,1994,2024,2053,2064,2066,2075,2077,2081,2084,2088,2101,2105,2108,2112,2115,2119,2141],[11,1506,1507,1508,1511],{},"The fastest way to extract design tokens from a website in Python is Playwright plus ",[20,1509,1510],{},"getComputedStyle()",": load the page in a real browser, collect the computed styles of every element, and count what repeats. The counting is the important part. A site's design system is whatever it uses everywhere, not every value that appears once in a forgotten footer.",[11,1513,1514],{},"Parsing CSS files without a browser is the approach most tutorials suggest, and it is the one that works worst on modern sites. More on that below.",[28,1516,1518],{"id":1517},"extract-colors-with-playwright","Extract colors with Playwright",[11,1520,1521],{},"First, install Playwright and a browser:",[33,1523,1525],{"className":398,"code":1524,"language":400,"meta":38,"style":38},"pip install playwright\nplaywright install chromium\n",[20,1526,1527,1536],{"__ignoreMap":38},[42,1528,1529,1531,1533],{"class":44,"line":45},[42,1530,408],{"class":407},[42,1532,411],{"class":87},[42,1534,1535],{"class":87}," playwright\n",[42,1537,1538,1540,1542],{"class":44,"line":52},[42,1539,885],{"class":407},[42,1541,411],{"class":87},[42,1543,424],{"class":87},[11,1545,1546],{},"Then collect styles from every element and count them:",[33,1548,1550],{"className":427,"code":1549,"language":429,"meta":38,"style":38},"from collections import Counter\nfrom playwright.sync_api import sync_playwright\n\nCOLLECT = \"\"\"\n() => {\n  const els = [...document.querySelectorAll('body *')].slice(0, 5000);\n  const out = { colors: [], fonts: [], sizes: [], radii: [], shadows: [] };\n  for (const el of els) {\n    const s = getComputedStyle(el);\n    out.colors.push(s.color, s.backgroundColor, s.borderTopColor);\n    out.fonts.push(s.fontFamily);\n    out.sizes.push(s.fontSize);\n    out.radii.push(s.borderTopLeftRadius);\n    out.shadows.push(s.boxShadow);\n  }\n  return out;\n}\n\"\"\"\n\ndef top(values, n=8, skip=(\"\", \"none\", \"normal\", \"auto\", \"0px\", \"rgba(0, 0, 0, 0)\")):\n    return Counter(v for v in values if v not in skip).most_common(n)\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    raw = page.evaluate(COLLECT)\n    browser.close()\n\nfor name in (\"colors\", \"fonts\", \"sizes\", \"radii\", \"shadows\"):\n    print(f\"\\n== {name} ==\")\n    for value, count in top(raw[name]):\n        print(f\"{count:>5}  {value}\")\n",[20,1551,1552,1556,1560,1564,1568,1573,1578,1583,1588,1593,1598,1603,1608,1613,1618,1623,1628,1633,1637,1641,1646,1651,1655,1660,1665,1670,1675,1681,1686,1691,1697,1703,1709],{"__ignoreMap":38},[42,1553,1554],{"class":44,"line":45},[42,1555,972],{},[42,1557,1558],{"class":44,"line":52},[42,1559,436],{},[42,1561,1562],{"class":44,"line":125},[42,1563,442],{"emptyLinePlaceholder":441},[42,1565,1566],{"class":44,"line":156},[42,1567,985],{},[42,1569,1570],{"class":44,"line":203},[42,1571,1572],{},"() => {\n",[42,1574,1575],{"class":44,"line":215},[42,1576,1577],{},"  const els = [...document.querySelectorAll('body *')].slice(0, 5000);\n",[42,1579,1580],{"class":44,"line":465},[42,1581,1582],{},"  const out = { colors: [], fonts: [], sizes: [], radii: [], shadows: [] };\n",[42,1584,1585],{"class":44,"line":471},[42,1586,1587],{},"  for (const el of els) {\n",[42,1589,1590],{"class":44,"line":477},[42,1591,1592],{},"    const s = getComputedStyle(el);\n",[42,1594,1595],{"class":44,"line":483},[42,1596,1597],{},"    out.colors.push(s.color, s.backgroundColor, s.borderTopColor);\n",[42,1599,1600],{"class":44,"line":489},[42,1601,1602],{},"    out.fonts.push(s.fontFamily);\n",[42,1604,1605],{"class":44,"line":494},[42,1606,1607],{},"    out.sizes.push(s.fontSize);\n",[42,1609,1610],{"class":44,"line":500},[42,1611,1612],{},"    out.radii.push(s.borderTopLeftRadius);\n",[42,1614,1615],{"class":44,"line":506},[42,1616,1617],{},"    out.shadows.push(s.boxShadow);\n",[42,1619,1620],{"class":44,"line":512},[42,1621,1622],{},"  }\n",[42,1624,1625],{"class":44,"line":518},[42,1626,1627],{},"  return out;\n",[42,1629,1630],{"class":44,"line":524},[42,1631,1632],{},"}\n",[42,1634,1635],{"class":44,"line":530},[42,1636,1010],{},[42,1638,1639],{"class":44,"line":536},[42,1640,442],{"emptyLinePlaceholder":441},[42,1642,1643],{"class":44,"line":541},[42,1644,1645],{},"def top(values, n=8, skip=(\"\", \"none\", \"normal\", \"auto\", \"0px\", \"rgba(0, 0, 0, 0)\")):\n",[42,1647,1648],{"class":44,"line":547},[42,1649,1650],{},"    return Counter(v for v in values if v not in skip).most_common(n)\n",[42,1652,1653],{"class":44,"line":553},[42,1654,442],{"emptyLinePlaceholder":441},[42,1656,1658],{"class":44,"line":1657},23,[42,1659,447],{},[42,1661,1663],{"class":44,"line":1662},24,[42,1664,452],{},[42,1666,1668],{"class":44,"line":1667},25,[42,1669,925],{},[42,1671,1673],{"class":44,"line":1672},26,[42,1674,480],{},[42,1676,1678],{"class":44,"line":1677},27,[42,1679,1680],{},"    raw = page.evaluate(COLLECT)\n",[42,1682,1684],{"class":44,"line":1683},28,[42,1685,533],{},[42,1687,1689],{"class":44,"line":1688},29,[42,1690,442],{"emptyLinePlaceholder":441},[42,1692,1694],{"class":44,"line":1693},30,[42,1695,1696],{},"for name in (\"colors\", \"fonts\", \"sizes\", \"radii\", \"shadows\"):\n",[42,1698,1700],{"class":44,"line":1699},31,[42,1701,1702],{},"    print(f\"\\n== {name} ==\")\n",[42,1704,1706],{"class":44,"line":1705},32,[42,1707,1708],{},"    for value, count in top(raw[name]):\n",[42,1710,1712],{"class":44,"line":1711},33,[42,1713,1714],{},"        print(f\"{count:>5}  {value}\")\n",[11,1716,1717],{},"Run it and you get something like:",[33,1719,1724],{"className":1720,"code":1722,"language":1723},[1721],"language-text","== colors ==\n 3021  rgb(255, 255, 255)\n 1204  rgb(26, 27, 37)\n  412  rgb(99, 91, 255)\n  118  rgb(66, 84, 102)\n","text",[20,1725,1722],{"__ignoreMap":38},[11,1727,1728,1729,1731,1732,1735,1736,1739],{},"That third line is the brand color, found by nothing smarter than counting. ",[20,1730,1432],{}," has already done the hard work for you: it resolved every CSS variable chain, every ",[20,1733,1734],{},"var(--a, var(--b))"," fallback, every cascade conflict, and turned Tailwind utility soup into plain ",[20,1737,1738],{},"rgb()"," values.",[28,1741,1743],{"id":1742},"fonts-and-the-type-scale","Fonts and the type scale",[11,1745,1746],{},"The same collection already has them. Font families come back as full stacks, so take the first entry:",[33,1748,1750],{"className":427,"code":1749,"language":429,"meta":38,"style":38},"families = Counter(f.split(\",\")[0].strip('\"\\' ') for f in raw[\"fonts\"])\nprint(families.most_common(5))\n\nsizes = sorted(\n    {float(s.replace(\"px\", \"\")) for s, c in top(raw[\"sizes\"], n=20)},\n)\nprint(sizes)  # e.g. [12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 36.0, 56.0]\n",[20,1751,1752,1757,1762,1766,1771,1776,1780],{"__ignoreMap":38},[42,1753,1754],{"class":44,"line":45},[42,1755,1756],{},"families = Counter(f.split(\",\")[0].strip('\"\\' ') for f in raw[\"fonts\"])\n",[42,1758,1759],{"class":44,"line":52},[42,1760,1761],{},"print(families.most_common(5))\n",[42,1763,1764],{"class":44,"line":125},[42,1765,442],{"emptyLinePlaceholder":441},[42,1767,1768],{"class":44,"line":156},[42,1769,1770],{},"sizes = sorted(\n",[42,1772,1773],{"class":44,"line":203},[42,1774,1775],{},"    {float(s.replace(\"px\", \"\")) for s, c in top(raw[\"sizes\"], n=20)},\n",[42,1777,1778],{"class":44,"line":215},[42,1779,282],{},[42,1781,1782],{"class":44,"line":465},[42,1783,1784],{},"print(sizes)  # e.g. [12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 36.0, 56.0]\n",[11,1786,1787],{},"That sorted list is the site's type scale. If it has fifteen entries instead of seven, you are looking at a site without a design system, which is also useful to know.",[28,1789,1791],{"id":1790},"from-raw-values-to-actual-tokens","From raw values to actual tokens",[11,1793,1794],{},"Raw counts are not tokens yet. Three cleanup steps matter:",[1796,1797,1798,1809,1822],"ol",{},[1799,1800,1801,1804,1805,1808],"li",{},[15,1802,1803],{},"Drop the defaults."," ",[20,1806,1807],{},"rgb(0, 0, 0)"," near the top does not mean the brand is black. Browser resets and inherited defaults pollute every element, so check whether a value actually appears on visible, styled elements before promoting it.",[1799,1810,1811,1804,1814,1817,1818,1821],{},[15,1812,1813],{},"Merge near-duplicates.",[20,1815,1816],{},"rgb(99, 91, 255)"," and ",[20,1819,1820],{},"rgb(99, 92, 255)"," are the same token with a rounding disagreement. Snap channels to a small tolerance before counting, or you will split one token's count across five entries.",[1799,1823,1824,1827,1828,1831,1832,1834],{},[15,1825,1826],{},"Name by role, not by value."," The most frequent saturated color on buttons and links is ",[20,1829,1830],{},"primary",". The dominant text color is ",[20,1833,1723],{},". Frequency plus element context gets you there; k-means clustering is overkill for this and I would skip it.",[28,1836,1838],{"id":1837},"common-problems","Common problems",[11,1840,1841],{},"Real gotchas, in the order they will bite you:",[1843,1844,1845,1855,1861,1875,1886],"ul",{},[1799,1846,1847,1850,1851,1854],{},[15,1848,1849],{},"Your viewport is a filter."," Computed styles reflect the current viewport only. A ",[20,1852,1853],{},"@media (max-width: 640px)"," override is invisible at 1440px. Extract at two viewports (mobile and desktop) if you care about responsive tokens.",[1799,1856,1857,1860],{},[15,1858,1859],{},"Cookie banners poison the palette."," Consent overlays and chat widgets bring their own colors and fonts, and they are often thousands of DOM nodes. Dismiss them or exclude their containers before collecting.",[1799,1862,1863,1804,1866,1868,1869,1871,1872,1874],{},[15,1864,1865],{},"Fonts can lie.",[20,1867,22],{}," lists what CSS asked for, not what loaded. With ",[20,1870,566],{}," the brand font may never arrive in a headless run. Await ",[20,1873,562],{}," before collecting if exact loaded fonts matter.",[1799,1876,1877,1882,1883,1885],{},[15,1878,1879,1880,60],{},"Gradients are not in ",[20,1881,1147],{}," They live in ",[20,1884,1342],{},". If a brand is gradient-heavy, collect that property too or you will report their hero as plain white.",[1799,1887,1888,1891,1892,1894,1895,1898],{},[15,1889,1890],{},"Shadow DOM and iframes are invisible"," to ",[20,1893,78],{},". Web-component-heavy sites need you to walk ",[20,1896,1897],{},"shadowRoot","s explicitly.",[28,1900,1902],{"id":1901},"diy-vs-parsing-css-vs-an-api","DIY vs parsing CSS vs an API",[577,1904,1905,1927],{},[580,1906,1907],{},[583,1908,1909,1912,1915,1918,1921,1924],{},[586,1910,1911],{},"Approach",[586,1913,1914],{},"Tailwind / utility CSS",[586,1916,1917],{},"Runtime CSS-in-JS",[586,1919,1920],{},"Resolves var() and cascade",[586,1922,1923],{},"Media queries",[586,1925,1926],{},"Maintenance",[599,1928,1929,1946,1964],{},[583,1930,1931,1934,1936,1938,1940,1943],{},[604,1932,1933],{},"Playwright + computed styles (this guide)",[604,1935,609],{},[604,1937,609],{},[604,1939,609],{},[604,1941,1942],{},"Current viewport only",[604,1944,1945],{},"Browser infra is yours to run",[583,1947,1948,1951,1954,1956,1958,1961],{},[604,1949,1950],{},"Parse CSS files yourself (tinycss2, no browser)",[604,1952,1953],{},"Painful",[604,1955,612],{},[604,1957,612],{},[604,1959,1960],{},"Declared, but unused values included",[604,1962,1963],{},"Low, but wrong on modern sites",[583,1965,1966,1969,1971,1974,1976,1979],{},[604,1967,1968],{},"MiroMiro extract API",[604,1970,609],{},[604,1972,1973],{},"No (static HTML + CSS, no JS execution)",[604,1975,609],{},[604,1977,1978],{},"Yes, all declared breakpoints",[604,1980,1981],{},"None",[11,1983,1984,1985,1988],{},"Use the Playwright approach if you are extracting from a handful of sites, need runtime-injected styles, and want full control. Skip the hand-rolled CSS-parsing route: resolving the cascade, specificity, and nested ",[20,1986,1987],{},"var()"," fallbacks yourself is a project, not a script.",[28,1990,663],{"id":662},[11,1992,1993],{},"The dedupe, the ranking, the var() chains, the resets, and every declared breakpoint - that is what our extract endpoint does for a living:",[33,1995,1997],{"className":398,"code":1996,"language":400,"meta":38,"style":38},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[20,1998,1999,2012],{"__ignoreMap":38},[42,2000,2001,2003,2005,2008,2010],{"class":44,"line":45},[42,2002,680],{"class":407},[42,2004,683],{"class":59},[42,2006,2007],{"class":87},"https://miromiro.app/api/v1/extract?url=stripe.com",[42,2009,689],{"class":59},[42,2011,692],{"class":55},[42,2013,2014,2016,2018,2020,2022],{"class":44,"line":52},[42,2015,697],{"class":87},[42,2017,683],{"class":59},[42,2019,702],{"class":87},[42,2021,705],{"class":55},[42,2023,708],{"class":59},[11,2025,2026,2027,2029,2030,2029,2033,2029,2036,2029,2039,2029,2042,2029,2045,2048,2049,2052],{},"The JSON response has ",[20,2028,1497],{},", ",[20,2031,2032],{},"fontFamilies",[20,2034,2035],{},"fontSizes",[20,2037,2038],{},"fontWeights",[20,2040,2041],{},"spacing",[20,2043,2044],{},"borderRadiuses",[20,2046,2047],{},"boxShadows",", and more, already deduped and ranked. Trim it with ",[20,2050,2051],{},"?fields=colors,spacing"," if you only need part of it.",[11,2054,2055,2056,2059,2060,60],{},"If your script from this guide breaks on some site's cookie banner at 2am, that is roughly the moment an API earns its keep. You can ",[236,2057,2058],{"href":724},"get a free API key"," with 100 credits a month, no card, or try it without signing up in the ",[236,2061,2063],{"href":2062},"/api/demo","playground",[28,2065,730],{"id":729},[11,2067,1429,2068,2070,2071,2074],{},[20,2069,1432],{}," plus a ",[20,2072,2073],{},"Counter"," is the honest way to extract design tokens in Python, and the frequency counting matters more than any clever clustering. Parse-the-CSS approaches are not worth your time on modern sites. I hope this saved you a debugging evening.",[28,2076,745],{"id":744},[747,2078,2080],{"id":2079},"can-i-extract-design-tokens-without-a-headless-browser","Can I extract design tokens without a headless browser?",[11,2082,2083],{},"You can parse the site's CSS with a library like tinycss2, but on modern sites this works badly: CSS-in-JS generates styles at runtime, Tailwind buries the palette in utility classes, and you cannot tell which declared values are actually used. Computed styles from a real browser are the ground truth.",[747,2085,2087],{"id":2086},"how-do-i-extract-tokens-from-a-tailwind-site-where-everything-is-utility-classes","How do I extract tokens from a Tailwind site where everything is utility classes?",[11,2089,2090,2091,2093,2094,1891,2097,2100],{},"Ignore the class names entirely and read computed styles. ",[20,2092,1432],{}," resolves ",[20,2095,2096],{},"bg-indigo-600",[20,2098,2099],{},"rgb(79, 70, 229)"," like any other CSS, so nothing in this guide changes.",[747,2102,2104],{"id":2103},"why-do-my-extracted-colors-include-values-the-site-never-visibly-uses","Why do my extracted colors include values the site never visibly uses?",[11,2106,2107],{},"Three usual suspects: browser defaults on elements you collected, cookie banners and chat widgets injecting their own palette, and transparent or inherited values you forgot to filter.",[747,2109,2111],{"id":2110},"how-many-pages-should-i-sample","How many pages should I sample?",[11,2113,2114],{},"One landing page is usually enough for brand color and typography. Add one product or app page for functional colors (success, error, info). More than three pages rarely changes the top tokens.",[28,2116,2118],{"id":2117},"related-reading","Related reading",[1843,2120,2121,2128,2134],{},[1799,2122,2123,2127],{},[236,2124,2126],{"href":2125},"/api/design-tokens-api","Design tokens API"," - the endpoint this post's shortcut uses",[1799,2129,2130,2133],{},[236,2131,2132],{"href":714},"Design tokens docs"," - full response shape and field list",[1799,2135,2136,2140],{},[236,2137,2139],{"href":2138},"/api/use-cases/ai-agent-ground-truth","Ground truth for AI agents"," - why agents need real tokens instead of guessing from screenshots",[788,2142,2143],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":38,"searchDepth":52,"depth":52,"links":2145},[2146,2147,2148,2149,2150,2151,2152,2153,2159],{"id":1517,"depth":52,"text":1518},{"id":1742,"depth":52,"text":1743},{"id":1790,"depth":52,"text":1791},{"id":1837,"depth":52,"text":1838},{"id":1901,"depth":52,"text":1902},{"id":662,"depth":52,"text":663},{"id":729,"depth":52,"text":730},{"id":744,"depth":52,"text":745,"children":2154},[2155,2156,2157,2158],{"id":2079,"depth":125,"text":2080},{"id":2086,"depth":125,"text":2087},{"id":2103,"depth":125,"text":2104},{"id":2110,"depth":125,"text":2111},{"id":2117,"depth":52,"text":2118},"Extract colors, fonts, spacing, and shadows from any website with Playwright in Python - working code, the gotchas that bite, and the one-API-call shortcut.",[2162,2164,2166,2168],{"question":2080,"answer":2163},"You can parse the site's CSS files with a library like tinycss2, but on modern sites this works badly. CSS-in-JS generates styles at runtime, utility-class sites like Tailwind bury the palette in thousands of class rules, and you cannot tell which declared values are actually used. Computed styles from a real browser are the ground truth.",{"question":2087,"answer":2165},"Ignore the class names entirely and read computed styles. getComputedStyle resolves bg-indigo-600 to rgb(79, 70, 229) the same way it resolves any other CSS, so the frequency-counting approach in this guide works unchanged.",{"question":2104,"answer":2167},"Three usual suspects: browser default styles on elements you collected, cookie banners and chat widgets injecting their own palette, and transparent or inherited values you forgot to filter. Count frequency, filter defaults, and dismiss overlays before collecting.",{"question":2169,"answer":2170},"How many pages should I sample to get a reliable palette?","One page is usually enough for brand color and typography if it is the homepage or a landing page. Add one product or app page for the functional palette (success, error, info states). More than three pages rarely changes the top tokens.",{},"/api-blog/how-to-extract-design-tokens-from-any-website-in-python",[2174,829,827],"best-design-token-extraction-tools-2026",{"title":1502,"description":2160},"api-blog/how-to-extract-design-tokens-from-any-website-in-python",[2178,429,885,2179,2180],"design tokens","css","web extraction","L5qnHk0I6Xw2LZ4KpLx52C6CU18QlatcV45aNNbr8F0",{"id":2183,"title":2184,"author":6,"body":2185,"category":2759,"date":807,"dateModified":808,"description":2760,"extension":810,"faqs":2761,"image":808,"meta":2772,"navigation":441,"path":2773,"readingTime":215,"relatedSlugs":2774,"seo":2775,"stem":2776,"tags":2777,"__hash__":2780},"apiBlog/api-blog/design-token-types-reference.md","Design Token Types: Every Token a Website Exposes (Reference)",{"type":8,"value":2186,"toc":2748},[2187,2190,2197,2201,2562,2566,2572,2584,2590,2599,2603,2610,2640,2700,2702,2706,2709,2713,2716,2720,2723,2727,2745],[11,2188,2189],{},"This is the reference we wish had existed when we built our extraction engine: every token type a live website exposes, the CSS properties each one comes from, and the gotcha that silently corrupts it. Seventeen groups, one table each way.",[11,2191,2192,2193,2196],{},"If you want the extraction code itself, that is in the ",[236,2194,2195],{"href":1064},"Python tokens guide",". This page is the map.",[28,2198,2200],{"id":2199},"the-complete-token-table","The complete token table",[577,2202,2203,2219],{},[580,2204,2205],{},[583,2206,2207,2210,2213,2216],{},[586,2208,2209],{},"Token group",[586,2211,2212],{},"CSS sources",[586,2214,2215],{},"What good output looks like",[586,2217,2218],{},"The gotcha",[599,2220,2221,2249,2274,2289,2309,2333,2349,2371,2393,2415,2431,2447,2463,2479,2501,2526,2545],{},[583,2222,2223,2226,2243,2246],{},[604,2224,2225],{},"Colors",[604,2227,2228,2029,2231,2029,2234,2029,2237,2029,2240],{},[20,2229,2230],{},"color",[20,2232,2233],{},"background-color",[20,2235,2236],{},"border-*-color",[20,2238,2239],{},"fill",[20,2241,2242],{},"stroke",[604,2244,2245],{},"5-12 ranked values, not 200",[604,2247,2248],{},"Browser defaults and cookie banners pollute counts",[583,2250,2251,2253,2261,2268],{},[604,2252,1333],{},[604,2254,2255,2029,2258],{},[20,2256,2257],{},"background-image",[20,2259,2260],{},"mask-image",[604,2262,2263,2264,2267],{},"Full ",[20,2265,2266],{},"linear-gradient(...)"," strings",[604,2269,2270,2271,2273],{},"Not in ",[20,2272,2233],{}," - miss this and gradient brands report as white",[583,2275,2276,2279,2283,2286],{},[604,2277,2278],{},"Font families",[604,2280,2281],{},[20,2282,22],{},[604,2284,2285],{},"1-3 families with roles (heading/body/mono)",[604,2287,2288],{},"The stack lists what CSS asked for, not what loaded",[583,2290,2291,2294,2299,2302],{},[604,2292,2293],{},"Font sizes",[604,2295,2296],{},[20,2297,2298],{},"font-size",[604,2300,2301],{},"A scale: 12, 14, 16, 20, 24, 36, 56",[604,2303,2304,2305,2308],{},"Fluid ",[20,2306,2307],{},"clamp()"," values resolve differently per viewport",[583,2310,2311,2314,2319,2322],{},[604,2312,2313],{},"Font weights",[604,2315,2316],{},[20,2317,2318],{},"font-weight",[604,2320,2321],{},"3-5 weights actually used",[604,2323,2324,2325,2328,2329,2332],{},"Keyword values (",[20,2326,2327],{},"bold",") and numbers (",[20,2330,2331],{},"700",") need normalizing",[583,2334,2335,2338,2343,2346],{},[604,2336,2337],{},"Line heights",[604,2339,2340],{},[20,2341,2342],{},"line-height",[604,2344,2345],{},"Unitless ratios per size tier",[604,2347,2348],{},"Computed styles return px; divide by font-size to recover the ratio",[583,2350,2351,2354,2359,2366],{},[604,2352,2353],{},"Letter spacing",[604,2355,2356],{},[20,2357,2358],{},"letter-spacing",[604,2360,2361,2362,2365],{},"Tight values on headings, ",[20,2363,2364],{},"normal"," elsewhere",[604,2367,2368,2370],{},[20,2369,2364],{}," is a value, not an absence",[583,2372,2373,2376,2387,2390],{},[604,2374,2375],{},"Spacing",[604,2377,2378,2029,2381,2029,2384],{},[20,2379,2380],{},"margin",[20,2382,2383],{},"padding",[20,2385,2386],{},"gap",[604,2388,2389],{},"A grid: 4, 8, 12, 16, 24, 32, 48, 64",[604,2391,2392],{},"Snap to the base unit or you get 40 near-duplicates",[583,2394,2395,2398,2403,2406],{},[604,2396,2397],{},"Border radii",[604,2399,2400],{},[20,2401,2402],{},"border-radius",[604,2404,2405],{},"2-4 values (subtle, card, pill)",[604,2407,2408,1817,2411,2414],{},[20,2409,2410],{},"9999px",[20,2412,2413],{},"50%"," are the same intent, different strings",[583,2416,2417,2420,2425,2428],{},[604,2418,2419],{},"Border widths",[604,2421,2422],{},[20,2423,2424],{},"border-width",[604,2426,2427],{},"1-2 values",[604,2429,2430],{},"Mostly 1px everywhere; the outliers are the signal",[583,2432,2433,2436,2441,2444],{},[604,2434,2435],{},"Box shadows",[604,2437,2438],{},[20,2439,2440],{},"box-shadow",[604,2442,2443],{},"2-4 elevation levels",[604,2445,2446],{},"Multi-layer shadows are one token, not four",[583,2448,2449,2452,2457,2460],{},[604,2450,2451],{},"Text shadows",[604,2453,2454],{},[20,2455,2456],{},"text-shadow",[604,2458,2459],{},"Usually empty; notable when not",[604,2461,2462],{},"Rare enough that any hit is worth keeping",[583,2464,2465,2468,2473,2476],{},[604,2466,2467],{},"Opacities",[604,2469,2470],{},[20,2471,2472],{},"opacity",[604,2474,2475],{},"Disabled/overlay/hover steps",[604,2477,2478],{},"Transition intermediate values sneak in if you sample mid-animation",[583,2480,2481,2484,2492,2495],{},[604,2482,2483],{},"Durations",[604,2485,2486,2029,2489],{},[20,2487,2488],{},"transition-duration",[20,2490,2491],{},"animation-duration",[604,2493,2494],{},"150ms/300ms/500ms tiers",[604,2496,2497,2500],{},[20,2498,2499],{},"0s"," is a default, not a token",[583,2502,2503,2506,2514,2520],{},[604,2504,2505],{},"Easings",[604,2507,2508,2029,2511],{},[20,2509,2510],{},"transition-timing-function",[20,2512,2513],{},"animation-timing-function",[604,2515,2516,2517],{},"The brand's ",[20,2518,2519],{},"cubic-bezier(...)",[604,2521,2522,2525],{},[20,2523,2524],{},"ease"," defaults drown the custom curves - filter them",[583,2527,2528,2531,2536,2539],{},[604,2529,2530],{},"Z-index layers",[604,2532,2533],{},[20,2534,2535],{},"z-index",[604,2537,2538],{},"The layering scale: 10, 50, 100, 9999",[604,2540,2541,2544],{},[20,2542,2543],{},"auto"," everywhere except the 5 values that matter",[583,2546,2547,2550,2556,2559],{},[604,2548,2549],{},"Breakpoints",[604,2551,2552,2555],{},[20,2553,2554],{},"@media"," conditions",[604,2557,2558],{},"3-5 widths: 640, 768, 1024, 1280",[604,2560,2561],{},"Only visible in stylesheets - computed styles never expose them",[28,2563,2565],{"id":2564},"the-same-table-by-extraction-difficulty","The same table, by extraction difficulty",[11,2567,2568,2571],{},[15,2569,2570],{},"Free (one property read):"," colors, font families, font sizes, font weights, opacities, z-index.",[11,2573,2574,2577,2578,2580,2581,2583],{},[15,2575,2576],{},"Needs normalizing:"," line heights (px to ratio), spacing (grid snapping), radii (",[20,2579,2410],{}," = ",[20,2582,2413],{}," = pill), weights (keywords to numbers), shadows (multi-layer grouping).",[11,2585,2586,2589],{},[15,2587,2588],{},"Needs the stylesheet, not computed styles:"," breakpoints, and any media-query variant of everything else. This is the structural reason a computed-styles-only extractor reports one viewport and calls it the design system.",[11,2591,2592,2595,2596,2598],{},[15,2593,2594],{},"Needs judgment:"," which color is ",[20,2597,1830],{}," (frequency plus element context - what is on the buttons), which durations are intentional vs defaults, which fonts actually loaded.",[28,2600,2602],{"id":2601},"how-this-maps-to-the-api","How this maps to the API",[11,2604,2605,2606,2609],{},"Every group in the table is a field on our extract endpoint, selectable with ",[20,2607,2608],{},"?fields=",":",[33,2611,2613],{"className":398,"code":2612,"language":400,"meta":38,"style":38},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,spacing,easings\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[20,2614,2615,2628],{"__ignoreMap":38},[42,2616,2617,2619,2621,2624,2626],{"class":44,"line":45},[42,2618,680],{"class":407},[42,2620,683],{"class":59},[42,2622,2623],{"class":87},"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,spacing,easings",[42,2625,689],{"class":59},[42,2627,692],{"class":55},[42,2629,2630,2632,2634,2636,2638],{"class":44,"line":52},[42,2631,697],{"class":87},[42,2633,683],{"class":59},[42,2635,702],{"class":87},[42,2637,705],{"class":55},[42,2639,708],{"class":59},[11,2641,2642,2643,2029,2645,2029,2648,2029,2650,2029,2652,2029,2654,2029,2657,2029,2660,2029,2662,2029,2664,2029,2667,2029,2669,2029,2672,2029,2675,2029,2678,2029,2681,2029,2684,2687,2688,2691,2692,2695,2696,2699],{},"The full field list: ",[20,2644,1497],{},[20,2646,2647],{},"gradients",[20,2649,2032],{},[20,2651,2035],{},[20,2653,2038],{},[20,2655,2656],{},"lineHeights",[20,2658,2659],{},"letterSpacings",[20,2661,2041],{},[20,2663,2044],{},[20,2665,2666],{},"borderWidths",[20,2668,2047],{},[20,2670,2671],{},"textShadows",[20,2673,2674],{},"opacities",[20,2676,2677],{},"durations",[20,2679,2680],{},"easings",[20,2682,2683],{},"zIndexes",[20,2685,2686],{},"breakpoints",". Add ",[20,2689,2690],{},"format=css"," or ",[20,2693,2694],{},"format=tailwind"," to get the same data as custom properties or a theme block instead of JSON. One call is 10 credits; ",[236,2697,2698],{"href":724},"100 credits a month are free",", no card.",[28,2701,745],{"id":744},[747,2703,2705],{"id":2704},"what-are-the-main-types-of-design-tokens","What are the main types of design tokens?",[11,2707,2708],{},"Color, typography (families/sizes/weights/line heights/letter spacing), spacing, borders (radii/widths), effects (shadows/gradients/opacities), motion (durations/easings), and structure (z-index, breakpoints). Seventeen groups in total from a live site.",[747,2710,2712],{"id":2711},"are-css-custom-properties-the-same-as-design-tokens","Are CSS custom properties the same as design tokens?",[11,2714,2715],{},"No. Custom properties are one storage format. Tailwind and CSS-in-JS sites often ship almost none while having rigorous token systems. Extract resolved values, not variable names.",[747,2717,2719],{"id":2718},"which-tokens-matter-most","Which tokens matter most?",[11,2721,2722],{},"Colors, type scale, spacing - in that order. They carry the identity. Radii, shadows, and motion are what make a rebuild feel like the original instead of a wireframe of it.",[747,2724,2726],{"id":2725},"can-i-extract-motion-tokens-from-a-website","Can I extract motion tokens from a website?",[11,2728,2729,2730,2029,2732,2734,2735,2737,2738,2740,2741,1817,2743,60],{},"Yes: ",[20,2731,2488],{},[20,2733,2491],{},", and their timing functions. Filter out ",[20,2736,2499],{}," and default ",[20,2739,2524],{}," or the real values drown. Our extract endpoint returns them as ",[20,2742,2677],{},[20,2744,2680],{},[788,2746,2747],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":38,"searchDepth":52,"depth":52,"links":2749},[2750,2751,2752,2753],{"id":2199,"depth":52,"text":2200},{"id":2564,"depth":52,"text":2565},{"id":2601,"depth":52,"text":2602},{"id":744,"depth":52,"text":745,"children":2754},[2755,2756,2757,2758],{"id":2704,"depth":125,"text":2705},{"id":2711,"depth":125,"text":2712},{"id":2718,"depth":125,"text":2719},{"id":2725,"depth":125,"text":2726},"Reference","All 17 design token types a website exposes - the CSS properties each comes from, what good output looks like, and the gotcha that silently corrupts it.",[2762,2764,2767,2769],{"question":2705,"answer":2763},"Color tokens, typography tokens (families, sizes, weights, line heights, letter spacing), spacing tokens, border tokens (radii, widths), effect tokens (shadows, gradients, opacities), motion tokens (durations, easings), and structural tokens (z-index layers, breakpoints). A complete extraction from a live site covers around 17 distinct groups.",{"question":2765,"answer":2766},"Which CSS properties do design tokens come from?","Each token group maps to specific CSS properties: colors from color, background-color and border-color; the type scale from font-size; spacing from margin, padding and gap; radii from border-radius; shadows from box-shadow; motion from transition-duration, animation-duration and their timing functions; breakpoints from media query conditions.",{"question":2712,"answer":2768},"No. Custom properties are one way a site stores tokens. Many production sites - especially Tailwind and CSS-in-JS codebases - ship few or no custom properties while still having a rigorous token system. Extract from resolved values, not variable names, or you will miss most real-world systems.",{"question":2770,"answer":2771},"What order should I extract tokens in?","Colors, type scale, and spacing first - they carry most of a site's identity and are enough to theme a rough UI. Radii, shadows, and motion are the polish layer that makes a rebuild feel like the original.",{},"/api-blog/design-token-types-reference",[828,2174,1492],{"title":2184,"description":2760},"api-blog/design-token-types-reference",[2178,2778,2179,2779],"reference","cheatsheet","Q2H5fOPEU9oXwkGDkaF1v9diKxKTdrdCh3xo9PqdAaM",1784751617447]