[{"data":1,"prerenderedAt":4360},["ShallowReactive",2],{"api-blog-convert-html-to-elementor-json-template":3,"api-blog-related-convert-html-to-elementor-json-template":2436},{"id":4,"title":5,"author":6,"body":7,"category":2400,"date":2401,"dateModified":2401,"description":2402,"extension":2403,"faqs":2404,"image":2417,"meta":2418,"navigation":533,"path":2419,"readingTime":141,"relatedSlugs":2420,"seo":2425,"seoTitle":2426,"stem":2427,"tags":2428,"__hash__":2435},"apiBlog/api-blog/convert-html-to-elementor-json-template.md","How to Convert HTML to an Elementor JSON Template (Format, Script, API)","Soraia",{"type":8,"value":9,"toc":2383},"minimark",[10,19,22,27,30,416,419,460,464,470,1830,1833,1857,1873,1877,1909,1913,1916,1944,1969,1975,2056,2070,2080,2090,2094,2097,2100,2104,2185,2188,2192,2198,2257,2267,2270,2278,2286,2290,2293,2297,2302,2326,2330,2333,2337,2350,2354,2357,2361,2379],[11,12,13,14,18],"p",{},"An Elementor template is a JSON file. To convert HTML into one, put your markup and CSS inside a single HTML widget, wrap that widget in a container, and save the result with ",[15,16,17],"code",{},"\"version\": \"0.4\"",". Elementor imports it under Templates, then Saved Templates.",[11,20,21],{},"That is the whole trick. The rest of this post is the exact format, a script that does it, and the five things that go wrong after import.",[23,24,26],"h2",{"id":25},"the-template-format","The template format",[11,28,29],{},"This is the smallest file Elementor accepts:",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"version\": \"0.4\",\n  \"title\": \"Pricing section\",\n  \"type\": \"section\",\n  \"content\": [\n    {\n      \"id\": \"a1b2c3d\",\n      \"elType\": \"container\",\n      \"settings\": { \"content_width\": \"full\" },\n      \"elements\": [\n        {\n          \"id\": \"e4f5a6b\",\n          \"elType\": \"widget\",\n          \"widgetType\": \"html\",\n          \"settings\": { \"html\": \"\u003Cdiv class=\\\"pricing\\\">…\u003C/div>\u003Cstyle>.pricing{…}\u003C/style>\" },\n          \"elements\": []\n        }\n      ],\n      \"isInner\": false\n    }\n  ],\n  \"page_settings\": []\n}\n","json","",[15,38,39,48,76,97,118,133,139,162,183,218,232,238,259,279,300,343,357,363,369,384,390,396,410],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMK4o","{\n",[40,49,51,54,58,61,64,67,71,73],{"class":42,"line":50},2,[40,52,53],{"class":46},"  \"",[40,55,57],{"class":56},"spNyl","version",[40,59,60],{"class":46},"\"",[40,62,63],{"class":46},":",[40,65,66],{"class":46}," \"",[40,68,70],{"class":69},"sfazB","0.4",[40,72,60],{"class":46},[40,74,75],{"class":46},",\n",[40,77,79,81,84,86,88,90,93,95],{"class":42,"line":78},3,[40,80,53],{"class":46},[40,82,83],{"class":56},"title",[40,85,60],{"class":46},[40,87,63],{"class":46},[40,89,66],{"class":46},[40,91,92],{"class":69},"Pricing section",[40,94,60],{"class":46},[40,96,75],{"class":46},[40,98,100,102,105,107,109,111,114,116],{"class":42,"line":99},4,[40,101,53],{"class":46},[40,103,104],{"class":56},"type",[40,106,60],{"class":46},[40,108,63],{"class":46},[40,110,66],{"class":46},[40,112,113],{"class":69},"section",[40,115,60],{"class":46},[40,117,75],{"class":46},[40,119,121,123,126,128,130],{"class":42,"line":120},5,[40,122,53],{"class":46},[40,124,125],{"class":56},"content",[40,127,60],{"class":46},[40,129,63],{"class":46},[40,131,132],{"class":46}," [\n",[40,134,136],{"class":42,"line":135},6,[40,137,138],{"class":46},"    {\n",[40,140,142,145,149,151,153,155,158,160],{"class":42,"line":141},7,[40,143,144],{"class":46},"      \"",[40,146,148],{"class":147},"sBMFI","id",[40,150,60],{"class":46},[40,152,63],{"class":46},[40,154,66],{"class":46},[40,156,157],{"class":69},"a1b2c3d",[40,159,60],{"class":46},[40,161,75],{"class":46},[40,163,165,167,170,172,174,176,179,181],{"class":42,"line":164},8,[40,166,144],{"class":46},[40,168,169],{"class":147},"elType",[40,171,60],{"class":46},[40,173,63],{"class":46},[40,175,66],{"class":46},[40,177,178],{"class":69},"container",[40,180,60],{"class":46},[40,182,75],{"class":46},[40,184,186,188,191,193,195,198,200,204,206,208,210,213,215],{"class":42,"line":185},9,[40,187,144],{"class":46},[40,189,190],{"class":147},"settings",[40,192,60],{"class":46},[40,194,63],{"class":46},[40,196,197],{"class":46}," {",[40,199,66],{"class":46},[40,201,203],{"class":202},"sbssI","content_width",[40,205,60],{"class":46},[40,207,63],{"class":46},[40,209,66],{"class":46},[40,211,212],{"class":69},"full",[40,214,60],{"class":46},[40,216,217],{"class":46}," },\n",[40,219,221,223,226,228,230],{"class":42,"line":220},10,[40,222,144],{"class":46},[40,224,225],{"class":147},"elements",[40,227,60],{"class":46},[40,229,63],{"class":46},[40,231,132],{"class":46},[40,233,235],{"class":42,"line":234},11,[40,236,237],{"class":46},"        {\n",[40,239,241,244,246,248,250,252,255,257],{"class":42,"line":240},12,[40,242,243],{"class":46},"          \"",[40,245,148],{"class":202},[40,247,60],{"class":46},[40,249,63],{"class":46},[40,251,66],{"class":46},[40,253,254],{"class":69},"e4f5a6b",[40,256,60],{"class":46},[40,258,75],{"class":46},[40,260,262,264,266,268,270,272,275,277],{"class":42,"line":261},13,[40,263,243],{"class":46},[40,265,169],{"class":202},[40,267,60],{"class":46},[40,269,63],{"class":46},[40,271,66],{"class":46},[40,273,274],{"class":69},"widget",[40,276,60],{"class":46},[40,278,75],{"class":46},[40,280,282,284,287,289,291,293,296,298],{"class":42,"line":281},14,[40,283,243],{"class":46},[40,285,286],{"class":202},"widgetType",[40,288,60],{"class":46},[40,290,63],{"class":46},[40,292,66],{"class":46},[40,294,295],{"class":69},"html",[40,297,60],{"class":46},[40,299,75],{"class":46},[40,301,303,305,307,309,311,313,315,318,320,322,324,327,331,334,336,339,341],{"class":42,"line":302},15,[40,304,243],{"class":46},[40,306,190],{"class":202},[40,308,60],{"class":46},[40,310,63],{"class":46},[40,312,197],{"class":46},[40,314,66],{"class":46},[40,316,295],{"class":317},"swJcz",[40,319,60],{"class":46},[40,321,63],{"class":46},[40,323,66],{"class":46},[40,325,326],{"class":69},"\u003Cdiv class=",[40,328,330],{"class":329},"sTEyZ","\\\"",[40,332,333],{"class":69},"pricing",[40,335,330],{"class":329},[40,337,338],{"class":69},">…\u003C/div>\u003Cstyle>.pricing{…}\u003C/style>",[40,340,60],{"class":46},[40,342,217],{"class":46},[40,344,346,348,350,352,354],{"class":42,"line":345},16,[40,347,243],{"class":46},[40,349,225],{"class":202},[40,351,60],{"class":46},[40,353,63],{"class":46},[40,355,356],{"class":46}," []\n",[40,358,360],{"class":42,"line":359},17,[40,361,362],{"class":46},"        }\n",[40,364,366],{"class":42,"line":365},18,[40,367,368],{"class":46},"      ],\n",[40,370,372,374,377,379,381],{"class":42,"line":371},19,[40,373,144],{"class":46},[40,375,376],{"class":147},"isInner",[40,378,60],{"class":46},[40,380,63],{"class":46},[40,382,383],{"class":46}," false\n",[40,385,387],{"class":42,"line":386},20,[40,388,389],{"class":46},"    }\n",[40,391,393],{"class":42,"line":392},21,[40,394,395],{"class":46},"  ],\n",[40,397,399,401,404,406,408],{"class":42,"line":398},22,[40,400,53],{"class":46},[40,402,403],{"class":56},"page_settings",[40,405,60],{"class":46},[40,407,63],{"class":46},[40,409,356],{"class":46},[40,411,413],{"class":42,"line":412},23,[40,414,415],{"class":46},"}\n",[11,417,418],{},"Four things to know:",[420,421,422,435,440,453],"ul",{},[423,424,425,427,428,430,431,434],"li",{},[15,426,104],{}," is ",[15,429,113],{}," for one block or ",[15,432,433],{},"page"," for a full page.",[423,436,437,439],{},[15,438,148],{}," values are short lowercase hex strings. They only need to be unique inside the file.",[423,441,442,445,446,448,449,452],{},[15,443,444],{},"elType: \"container\""," is the flexbox container. It has been the default layout element since Elementor 3.16. The older ",[15,447,113],{}," and ",[15,450,451],{},"column"," pair still imports, but it gets clamped to the theme's content width.",[423,454,455,456,459],{},"The HTML widget takes one string. Your CSS goes in a ",[15,457,458],{},"\u003Cstyle>"," tag inside that same string.",[23,461,463],{"id":462},"a-script-that-wraps-html-and-css","A script that wraps HTML and CSS",[11,465,466,467,63],{},"No dependencies. Save it as ",[15,468,469],{},"to-elementor.mjs",[31,471,475],{"className":472,"code":473,"language":474,"meta":36,"style":36},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { readFileSync, writeFileSync } from 'node:fs'\nimport { randomBytes } from 'node:crypto'\n\nconst [htmlFile, cssFile, out = 'template.json'] = process.argv.slice(2)\nconst html = readFileSync(htmlFile, 'utf8')\nconst css = cssFile ? readFileSync(cssFile, 'utf8') : ''\nconst id = () => randomBytes(4).toString('hex').slice(0, 7)\nconst scope = `imp-${id()}`\n\n// Prefix every selector so the theme and the block cannot restyle each other.\n// Walks the stylesheet block by block: @media and @supports are scoped inside,\n// other at-rules (@keyframes, @font-face) pass through untouched.\nfunction scopeCss(css) {\n  let out = ''\n  let i = 0\n  while (i \u003C css.length) {\n    const open = css.indexOf('{', i)\n    if (open === -1) break\n    const prelude = css.slice(i, open).trim()\n    let depth = 1\n    let j = open + 1\n    while (j \u003C css.length && depth) {\n      if (css[j] === '{') depth++\n      else if (css[j] === '}') depth--\n      j++\n    }\n    const body = css.slice(open + 1, j - 1)\n    if (/^@(media|supports)/.test(prelude)) out += `${prelude} {\n${scopeCss(body)}}\n`\n    else if (prelude.startsWith('@')) out += `${prelude} {${body}}\n`\n    else out += `${prelude.split(',').map(sel => `.${scope} ${sel.trim()}`).join(', ')} {${body}}\n`\n    i = j\n  }\n  return out\n}\nconst scoped = scopeCss(css)\n\nconst widget = `\u003Cdiv class=\"${scope}\">\\n${html}\\n\u003C/div>\\n\u003Cstyle>\\n${scoped}\\n\u003C/style>`\n\nconst template = {\n  version: '0.4',\n  title: 'Imported section',\n  type: 'section',\n  content: [{\n    id: id(),\n    elType: 'container',\n    settings: { content_width: 'full', padding: { unit: 'px', top: '0', right: '0', bottom: '0', left: '0', isLinked: true } },\n    elements: [{ id: id(), elType: 'widget', widgetType: 'html', settings: { html: widget }, elements: [] }],\n    isInner: false\n  }],\n  page_settings: []\n}\n\nwriteFileSync(out, JSON.stringify(template, null, 2))\nconsole.log(`Wrote ${out}`)\n","js",[15,476,477,509,529,535,597,622,658,715,741,745,751,756,761,780,792,804,830,862,886,919,932,948,975,1009,1044,1052,1057,1092,1150,1165,1171,1219,1224,1319,1324,1335,1341,1350,1355,1370,1375,1433,1438,1450,1466,1483,1499,1511,1526,1542,1651,1730,1740,1750,1760,1765,1770,1803],{"__ignoreMap":36},[40,478,479,483,485,488,491,494,497,500,503,506],{"class":42,"line":43},[40,480,482],{"class":481},"s7zQu","import",[40,484,197],{"class":46},[40,486,487],{"class":329}," readFileSync",[40,489,490],{"class":46},",",[40,492,493],{"class":329}," writeFileSync",[40,495,496],{"class":46}," }",[40,498,499],{"class":481}," from",[40,501,502],{"class":46}," '",[40,504,505],{"class":69},"node:fs",[40,507,508],{"class":46},"'\n",[40,510,511,513,515,518,520,522,524,527],{"class":42,"line":50},[40,512,482],{"class":481},[40,514,197],{"class":46},[40,516,517],{"class":329}," randomBytes",[40,519,496],{"class":46},[40,521,499],{"class":481},[40,523,502],{"class":46},[40,525,526],{"class":69},"node:crypto",[40,528,508],{"class":46},[40,530,531],{"class":42,"line":78},[40,532,534],{"emptyLinePlaceholder":533},true,"\n",[40,536,537,540,543,546,548,551,553,556,559,561,564,567,570,573,576,579,582,584,588,591,594],{"class":42,"line":99},[40,538,539],{"class":56},"const",[40,541,542],{"class":46}," [",[40,544,545],{"class":329},"htmlFile",[40,547,490],{"class":46},[40,549,550],{"class":329}," cssFile",[40,552,490],{"class":46},[40,554,555],{"class":329}," out ",[40,557,558],{"class":46},"=",[40,560,502],{"class":46},[40,562,563],{"class":69},"template.json",[40,565,566],{"class":46},"'",[40,568,569],{"class":46},"]",[40,571,572],{"class":46}," =",[40,574,575],{"class":329}," process",[40,577,578],{"class":46},".",[40,580,581],{"class":329},"argv",[40,583,578],{"class":46},[40,585,587],{"class":586},"s2Zo4","slice",[40,589,590],{"class":329},"(",[40,592,593],{"class":202},"2",[40,595,596],{"class":329},")\n",[40,598,599,601,604,606,608,611,613,615,618,620],{"class":42,"line":120},[40,600,539],{"class":56},[40,602,603],{"class":329}," html ",[40,605,558],{"class":46},[40,607,487],{"class":586},[40,609,610],{"class":329},"(htmlFile",[40,612,490],{"class":46},[40,614,502],{"class":46},[40,616,617],{"class":69},"utf8",[40,619,566],{"class":46},[40,621,596],{"class":329},[40,623,624,626,629,631,634,637,639,642,644,646,648,650,653,655],{"class":42,"line":135},[40,625,539],{"class":56},[40,627,628],{"class":329}," css ",[40,630,558],{"class":46},[40,632,633],{"class":329}," cssFile ",[40,635,636],{"class":46},"?",[40,638,487],{"class":586},[40,640,641],{"class":329},"(cssFile",[40,643,490],{"class":46},[40,645,502],{"class":46},[40,647,617],{"class":69},[40,649,566],{"class":46},[40,651,652],{"class":329},") ",[40,654,63],{"class":46},[40,656,657],{"class":46}," ''\n",[40,659,660,662,665,667,670,673,675,677,680,683,685,688,690,692,695,697,699,701,703,705,708,710,713],{"class":42,"line":141},[40,661,539],{"class":56},[40,663,664],{"class":329}," id ",[40,666,558],{"class":46},[40,668,669],{"class":46}," ()",[40,671,672],{"class":56}," =>",[40,674,517],{"class":586},[40,676,590],{"class":329},[40,678,679],{"class":202},"4",[40,681,682],{"class":329},")",[40,684,578],{"class":46},[40,686,687],{"class":586},"toString",[40,689,590],{"class":329},[40,691,566],{"class":46},[40,693,694],{"class":69},"hex",[40,696,566],{"class":46},[40,698,682],{"class":329},[40,700,578],{"class":46},[40,702,587],{"class":586},[40,704,590],{"class":329},[40,706,707],{"class":202},"0",[40,709,490],{"class":46},[40,711,712],{"class":202}," 7",[40,714,596],{"class":329},[40,716,717,719,722,724,727,730,733,735,738],{"class":42,"line":164},[40,718,539],{"class":56},[40,720,721],{"class":329}," scope ",[40,723,558],{"class":46},[40,725,726],{"class":46}," `",[40,728,729],{"class":69},"imp-",[40,731,732],{"class":46},"${",[40,734,148],{"class":586},[40,736,737],{"class":329},"()",[40,739,740],{"class":46},"}`\n",[40,742,743],{"class":42,"line":185},[40,744,534],{"emptyLinePlaceholder":533},[40,746,747],{"class":42,"line":220},[40,748,750],{"class":749},"sHwdD","// Prefix every selector so the theme and the block cannot restyle each other.\n",[40,752,753],{"class":42,"line":234},[40,754,755],{"class":749},"// Walks the stylesheet block by block: @media and @supports are scoped inside,\n",[40,757,758],{"class":42,"line":240},[40,759,760],{"class":749},"// other at-rules (@keyframes, @font-face) pass through untouched.\n",[40,762,763,766,769,771,775,777],{"class":42,"line":261},[40,764,765],{"class":56},"function",[40,767,768],{"class":586}," scopeCss",[40,770,590],{"class":46},[40,772,774],{"class":773},"sHdIc","css",[40,776,682],{"class":46},[40,778,779],{"class":46}," {\n",[40,781,782,785,788,790],{"class":42,"line":281},[40,783,784],{"class":56},"  let",[40,786,787],{"class":329}," out",[40,789,572],{"class":46},[40,791,657],{"class":46},[40,793,794,796,799,801],{"class":42,"line":302},[40,795,784],{"class":56},[40,797,798],{"class":329}," i",[40,800,572],{"class":46},[40,802,803],{"class":202}," 0\n",[40,805,806,809,812,815,818,821,823,826,828],{"class":42,"line":345},[40,807,808],{"class":481},"  while",[40,810,811],{"class":317}," (",[40,813,814],{"class":329},"i",[40,816,817],{"class":46}," \u003C",[40,819,820],{"class":329}," css",[40,822,578],{"class":46},[40,824,825],{"class":329},"length",[40,827,652],{"class":317},[40,829,47],{"class":46},[40,831,832,835,838,840,842,844,847,849,851,854,856,858,860],{"class":42,"line":359},[40,833,834],{"class":56},"    const",[40,836,837],{"class":329}," open",[40,839,572],{"class":46},[40,841,820],{"class":329},[40,843,578],{"class":46},[40,845,846],{"class":586},"indexOf",[40,848,590],{"class":317},[40,850,566],{"class":46},[40,852,853],{"class":69},"{",[40,855,566],{"class":46},[40,857,490],{"class":46},[40,859,798],{"class":329},[40,861,596],{"class":317},[40,863,864,867,869,872,875,878,881,883],{"class":42,"line":365},[40,865,866],{"class":481},"    if",[40,868,811],{"class":317},[40,870,871],{"class":329},"open",[40,873,874],{"class":46}," ===",[40,876,877],{"class":46}," -",[40,879,880],{"class":202},"1",[40,882,652],{"class":317},[40,884,885],{"class":481},"break\n",[40,887,888,890,893,895,897,899,901,903,905,907,909,911,913,916],{"class":42,"line":371},[40,889,834],{"class":56},[40,891,892],{"class":329}," prelude",[40,894,572],{"class":46},[40,896,820],{"class":329},[40,898,578],{"class":46},[40,900,587],{"class":586},[40,902,590],{"class":317},[40,904,814],{"class":329},[40,906,490],{"class":46},[40,908,837],{"class":329},[40,910,682],{"class":317},[40,912,578],{"class":46},[40,914,915],{"class":586},"trim",[40,917,918],{"class":317},"()\n",[40,920,921,924,927,929],{"class":42,"line":386},[40,922,923],{"class":56},"    let",[40,925,926],{"class":329}," depth",[40,928,572],{"class":46},[40,930,931],{"class":202}," 1\n",[40,933,934,936,939,941,943,946],{"class":42,"line":392},[40,935,923],{"class":56},[40,937,938],{"class":329}," j",[40,940,572],{"class":46},[40,942,837],{"class":329},[40,944,945],{"class":46}," +",[40,947,931],{"class":202},[40,949,950,953,955,958,960,962,964,966,969,971,973],{"class":42,"line":398},[40,951,952],{"class":481},"    while",[40,954,811],{"class":317},[40,956,957],{"class":329},"j",[40,959,817],{"class":46},[40,961,820],{"class":329},[40,963,578],{"class":46},[40,965,825],{"class":329},[40,967,968],{"class":46}," &&",[40,970,926],{"class":329},[40,972,652],{"class":317},[40,974,47],{"class":46},[40,976,977,980,982,984,987,989,992,995,997,999,1001,1003,1006],{"class":42,"line":412},[40,978,979],{"class":481},"      if",[40,981,811],{"class":317},[40,983,774],{"class":329},[40,985,986],{"class":317},"[",[40,988,957],{"class":329},[40,990,991],{"class":317},"] ",[40,993,994],{"class":46},"===",[40,996,502],{"class":46},[40,998,853],{"class":69},[40,1000,566],{"class":46},[40,1002,652],{"class":317},[40,1004,1005],{"class":329},"depth",[40,1007,1008],{"class":46},"++\n",[40,1010,1012,1015,1018,1020,1022,1024,1026,1028,1030,1032,1035,1037,1039,1041],{"class":42,"line":1011},24,[40,1013,1014],{"class":481},"      else",[40,1016,1017],{"class":481}," if",[40,1019,811],{"class":317},[40,1021,774],{"class":329},[40,1023,986],{"class":317},[40,1025,957],{"class":329},[40,1027,991],{"class":317},[40,1029,994],{"class":46},[40,1031,502],{"class":46},[40,1033,1034],{"class":69},"}",[40,1036,566],{"class":46},[40,1038,652],{"class":317},[40,1040,1005],{"class":329},[40,1042,1043],{"class":46},"--\n",[40,1045,1047,1050],{"class":42,"line":1046},25,[40,1048,1049],{"class":329},"      j",[40,1051,1008],{"class":46},[40,1053,1055],{"class":42,"line":1054},26,[40,1056,389],{"class":46},[40,1058,1060,1062,1065,1067,1069,1071,1073,1075,1077,1079,1082,1084,1086,1088,1090],{"class":42,"line":1059},27,[40,1061,834],{"class":56},[40,1063,1064],{"class":329}," body",[40,1066,572],{"class":46},[40,1068,820],{"class":329},[40,1070,578],{"class":46},[40,1072,587],{"class":586},[40,1074,590],{"class":317},[40,1076,871],{"class":329},[40,1078,945],{"class":46},[40,1080,1081],{"class":202}," 1",[40,1083,490],{"class":46},[40,1085,938],{"class":329},[40,1087,877],{"class":46},[40,1089,1081],{"class":202},[40,1091,596],{"class":317},[40,1093,1095,1097,1099,1102,1105,1108,1110,1113,1116,1119,1122,1124,1127,1129,1132,1135,1138,1141,1144,1146,1148],{"class":42,"line":1094},28,[40,1096,866],{"class":481},[40,1098,811],{"class":317},[40,1100,1101],{"class":46},"/",[40,1103,1104],{"class":481},"^",[40,1106,1107],{"class":69},"@",[40,1109,590],{"class":46},[40,1111,1112],{"class":69},"media",[40,1114,1115],{"class":46},"|",[40,1117,1118],{"class":69},"supports",[40,1120,1121],{"class":46},")/",[40,1123,578],{"class":46},[40,1125,1126],{"class":586},"test",[40,1128,590],{"class":317},[40,1130,1131],{"class":329},"prelude",[40,1133,1134],{"class":317},")) ",[40,1136,1137],{"class":329},"out",[40,1139,1140],{"class":46}," +=",[40,1142,1143],{"class":46}," `${",[40,1145,1131],{"class":329},[40,1147,1034],{"class":46},[40,1149,779],{"class":69},[40,1151,1153,1155,1158,1161,1163],{"class":42,"line":1152},29,[40,1154,732],{"class":46},[40,1156,1157],{"class":586},"scopeCss",[40,1159,1160],{"class":329},"(body)",[40,1162,1034],{"class":46},[40,1164,415],{"class":69},[40,1166,1168],{"class":42,"line":1167},30,[40,1169,1170],{"class":46},"`\n",[40,1172,1174,1177,1179,1181,1183,1185,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1215,1217],{"class":42,"line":1173},31,[40,1175,1176],{"class":481},"    else",[40,1178,1017],{"class":481},[40,1180,811],{"class":317},[40,1182,1131],{"class":329},[40,1184,578],{"class":46},[40,1186,1187],{"class":586},"startsWith",[40,1189,590],{"class":317},[40,1191,566],{"class":46},[40,1193,1107],{"class":69},[40,1195,566],{"class":46},[40,1197,1134],{"class":317},[40,1199,1137],{"class":329},[40,1201,1140],{"class":46},[40,1203,1143],{"class":46},[40,1205,1131],{"class":329},[40,1207,1034],{"class":46},[40,1209,197],{"class":69},[40,1211,732],{"class":46},[40,1213,1214],{"class":329},"body",[40,1216,1034],{"class":46},[40,1218,415],{"class":69},[40,1220,1222],{"class":42,"line":1221},32,[40,1223,1170],{"class":46},[40,1225,1227,1229,1231,1233,1235,1237,1239,1242,1244,1246,1248,1250,1252,1254,1257,1259,1262,1264,1266,1268,1270,1273,1275,1278,1280,1282,1284,1286,1289,1291,1293,1296,1298,1300,1303,1305,1307,1309,1311,1313,1315,1317],{"class":42,"line":1226},33,[40,1228,1176],{"class":481},[40,1230,787],{"class":329},[40,1232,1140],{"class":46},[40,1234,1143],{"class":46},[40,1236,1131],{"class":329},[40,1238,578],{"class":46},[40,1240,1241],{"class":586},"split",[40,1243,590],{"class":329},[40,1245,566],{"class":46},[40,1247,490],{"class":69},[40,1249,566],{"class":46},[40,1251,682],{"class":329},[40,1253,578],{"class":46},[40,1255,1256],{"class":586},"map",[40,1258,590],{"class":329},[40,1260,1261],{"class":773},"sel",[40,1263,672],{"class":56},[40,1265,726],{"class":46},[40,1267,578],{"class":69},[40,1269,732],{"class":46},[40,1271,1272],{"class":329},"scope",[40,1274,1034],{"class":46},[40,1276,1277],{"class":46}," ${",[40,1279,1261],{"class":329},[40,1281,578],{"class":46},[40,1283,915],{"class":586},[40,1285,737],{"class":329},[40,1287,1288],{"class":46},"}`",[40,1290,682],{"class":329},[40,1292,578],{"class":46},[40,1294,1295],{"class":586},"join",[40,1297,590],{"class":329},[40,1299,566],{"class":46},[40,1301,1302],{"class":69},", ",[40,1304,566],{"class":46},[40,1306,682],{"class":329},[40,1308,1034],{"class":46},[40,1310,197],{"class":69},[40,1312,732],{"class":46},[40,1314,1214],{"class":329},[40,1316,1034],{"class":46},[40,1318,415],{"class":69},[40,1320,1322],{"class":42,"line":1321},34,[40,1323,1170],{"class":46},[40,1325,1327,1330,1332],{"class":42,"line":1326},35,[40,1328,1329],{"class":329},"    i",[40,1331,572],{"class":46},[40,1333,1334],{"class":329}," j\n",[40,1336,1338],{"class":42,"line":1337},36,[40,1339,1340],{"class":46},"  }\n",[40,1342,1344,1347],{"class":42,"line":1343},37,[40,1345,1346],{"class":481},"  return",[40,1348,1349],{"class":329}," out\n",[40,1351,1353],{"class":42,"line":1352},38,[40,1354,415],{"class":46},[40,1356,1358,1360,1363,1365,1367],{"class":42,"line":1357},39,[40,1359,539],{"class":56},[40,1361,1362],{"class":329}," scoped ",[40,1364,558],{"class":46},[40,1366,768],{"class":586},[40,1368,1369],{"class":329},"(css)\n",[40,1371,1373],{"class":42,"line":1372},40,[40,1374,534],{"emptyLinePlaceholder":533},[40,1376,1378,1380,1383,1385,1387,1390,1392,1394,1396,1399,1402,1404,1406,1408,1410,1413,1415,1417,1419,1421,1424,1426,1428,1431],{"class":42,"line":1377},41,[40,1379,539],{"class":56},[40,1381,1382],{"class":329}," widget ",[40,1384,558],{"class":46},[40,1386,726],{"class":46},[40,1388,1389],{"class":69},"\u003Cdiv class=\"",[40,1391,732],{"class":46},[40,1393,1272],{"class":329},[40,1395,1034],{"class":46},[40,1397,1398],{"class":69},"\">",[40,1400,1401],{"class":329},"\\n",[40,1403,732],{"class":46},[40,1405,295],{"class":329},[40,1407,1034],{"class":46},[40,1409,1401],{"class":329},[40,1411,1412],{"class":69},"\u003C/div>",[40,1414,1401],{"class":329},[40,1416,458],{"class":69},[40,1418,1401],{"class":329},[40,1420,732],{"class":46},[40,1422,1423],{"class":329},"scoped",[40,1425,1034],{"class":46},[40,1427,1401],{"class":329},[40,1429,1430],{"class":69},"\u003C/style>",[40,1432,1170],{"class":46},[40,1434,1436],{"class":42,"line":1435},42,[40,1437,534],{"emptyLinePlaceholder":533},[40,1439,1441,1443,1446,1448],{"class":42,"line":1440},43,[40,1442,539],{"class":56},[40,1444,1445],{"class":329}," template ",[40,1447,558],{"class":46},[40,1449,779],{"class":46},[40,1451,1453,1456,1458,1460,1462,1464],{"class":42,"line":1452},44,[40,1454,1455],{"class":317},"  version",[40,1457,63],{"class":46},[40,1459,502],{"class":46},[40,1461,70],{"class":69},[40,1463,566],{"class":46},[40,1465,75],{"class":46},[40,1467,1469,1472,1474,1476,1479,1481],{"class":42,"line":1468},45,[40,1470,1471],{"class":317},"  title",[40,1473,63],{"class":46},[40,1475,502],{"class":46},[40,1477,1478],{"class":69},"Imported section",[40,1480,566],{"class":46},[40,1482,75],{"class":46},[40,1484,1486,1489,1491,1493,1495,1497],{"class":42,"line":1485},46,[40,1487,1488],{"class":317},"  type",[40,1490,63],{"class":46},[40,1492,502],{"class":46},[40,1494,113],{"class":69},[40,1496,566],{"class":46},[40,1498,75],{"class":46},[40,1500,1502,1505,1507,1509],{"class":42,"line":1501},47,[40,1503,1504],{"class":317},"  content",[40,1506,63],{"class":46},[40,1508,542],{"class":329},[40,1510,47],{"class":46},[40,1512,1514,1517,1519,1522,1524],{"class":42,"line":1513},48,[40,1515,1516],{"class":317},"    id",[40,1518,63],{"class":46},[40,1520,1521],{"class":586}," id",[40,1523,737],{"class":329},[40,1525,75],{"class":46},[40,1527,1529,1532,1534,1536,1538,1540],{"class":42,"line":1528},49,[40,1530,1531],{"class":317},"    elType",[40,1533,63],{"class":46},[40,1535,502],{"class":46},[40,1537,178],{"class":69},[40,1539,566],{"class":46},[40,1541,75],{"class":46},[40,1543,1545,1548,1550,1552,1555,1557,1559,1561,1563,1565,1568,1570,1572,1575,1577,1579,1582,1584,1586,1589,1591,1593,1595,1597,1599,1602,1604,1606,1608,1610,1612,1615,1617,1619,1621,1623,1625,1628,1630,1632,1634,1636,1638,1641,1643,1647,1649],{"class":42,"line":1544},50,[40,1546,1547],{"class":317},"    settings",[40,1549,63],{"class":46},[40,1551,197],{"class":46},[40,1553,1554],{"class":317}," content_width",[40,1556,63],{"class":46},[40,1558,502],{"class":46},[40,1560,212],{"class":69},[40,1562,566],{"class":46},[40,1564,490],{"class":46},[40,1566,1567],{"class":317}," padding",[40,1569,63],{"class":46},[40,1571,197],{"class":46},[40,1573,1574],{"class":317}," unit",[40,1576,63],{"class":46},[40,1578,502],{"class":46},[40,1580,1581],{"class":69},"px",[40,1583,566],{"class":46},[40,1585,490],{"class":46},[40,1587,1588],{"class":317}," top",[40,1590,63],{"class":46},[40,1592,502],{"class":46},[40,1594,707],{"class":69},[40,1596,566],{"class":46},[40,1598,490],{"class":46},[40,1600,1601],{"class":317}," right",[40,1603,63],{"class":46},[40,1605,502],{"class":46},[40,1607,707],{"class":69},[40,1609,566],{"class":46},[40,1611,490],{"class":46},[40,1613,1614],{"class":317}," bottom",[40,1616,63],{"class":46},[40,1618,502],{"class":46},[40,1620,707],{"class":69},[40,1622,566],{"class":46},[40,1624,490],{"class":46},[40,1626,1627],{"class":317}," left",[40,1629,63],{"class":46},[40,1631,502],{"class":46},[40,1633,707],{"class":69},[40,1635,566],{"class":46},[40,1637,490],{"class":46},[40,1639,1640],{"class":317}," isLinked",[40,1642,63],{"class":46},[40,1644,1646],{"class":1645},"sfNiH"," true",[40,1648,496],{"class":46},[40,1650,217],{"class":46},[40,1652,1654,1657,1659,1661,1663,1665,1667,1669,1671,1673,1676,1678,1680,1682,1684,1686,1689,1691,1693,1695,1697,1699,1702,1704,1706,1709,1711,1713,1716,1719,1721,1724,1726,1728],{"class":42,"line":1653},51,[40,1655,1656],{"class":317},"    elements",[40,1658,63],{"class":46},[40,1660,542],{"class":329},[40,1662,853],{"class":46},[40,1664,1521],{"class":317},[40,1666,63],{"class":46},[40,1668,1521],{"class":586},[40,1670,737],{"class":329},[40,1672,490],{"class":46},[40,1674,1675],{"class":317}," elType",[40,1677,63],{"class":46},[40,1679,502],{"class":46},[40,1681,274],{"class":69},[40,1683,566],{"class":46},[40,1685,490],{"class":46},[40,1687,1688],{"class":317}," widgetType",[40,1690,63],{"class":46},[40,1692,502],{"class":46},[40,1694,295],{"class":69},[40,1696,566],{"class":46},[40,1698,490],{"class":46},[40,1700,1701],{"class":317}," settings",[40,1703,63],{"class":46},[40,1705,197],{"class":46},[40,1707,1708],{"class":317}," html",[40,1710,63],{"class":46},[40,1712,1382],{"class":329},[40,1714,1715],{"class":46},"},",[40,1717,1718],{"class":317}," elements",[40,1720,63],{"class":46},[40,1722,1723],{"class":329}," [] ",[40,1725,1034],{"class":46},[40,1727,569],{"class":329},[40,1729,75],{"class":46},[40,1731,1733,1736,1738],{"class":42,"line":1732},52,[40,1734,1735],{"class":317},"    isInner",[40,1737,63],{"class":46},[40,1739,383],{"class":1645},[40,1741,1743,1746,1748],{"class":42,"line":1742},53,[40,1744,1745],{"class":46},"  }",[40,1747,569],{"class":329},[40,1749,75],{"class":46},[40,1751,1753,1756,1758],{"class":42,"line":1752},54,[40,1754,1755],{"class":317},"  page_settings",[40,1757,63],{"class":46},[40,1759,356],{"class":329},[40,1761,1763],{"class":42,"line":1762},55,[40,1764,415],{"class":46},[40,1766,1768],{"class":42,"line":1767},56,[40,1769,534],{"emptyLinePlaceholder":533},[40,1771,1773,1776,1779,1781,1784,1786,1789,1792,1794,1797,1800],{"class":42,"line":1772},57,[40,1774,1775],{"class":586},"writeFileSync",[40,1777,1778],{"class":329},"(out",[40,1780,490],{"class":46},[40,1782,1783],{"class":329}," JSON",[40,1785,578],{"class":46},[40,1787,1788],{"class":586},"stringify",[40,1790,1791],{"class":329},"(template",[40,1793,490],{"class":46},[40,1795,1796],{"class":46}," null,",[40,1798,1799],{"class":202}," 2",[40,1801,1802],{"class":329},"))\n",[40,1804,1806,1809,1811,1814,1816,1819,1822,1824,1826,1828],{"class":42,"line":1805},58,[40,1807,1808],{"class":329},"console",[40,1810,578],{"class":46},[40,1812,1813],{"class":586},"log",[40,1815,590],{"class":329},[40,1817,1818],{"class":46},"`",[40,1820,1821],{"class":69},"Wrote ",[40,1823,732],{"class":46},[40,1825,1137],{"class":329},[40,1827,1288],{"class":46},[40,1829,596],{"class":329},[11,1831,1832],{},"Run it:",[31,1834,1838],{"className":1835,"code":1836,"language":1837,"meta":36,"style":36},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","node to-elementor.mjs pricing.html pricing.css pricing.json\n","bash",[15,1839,1840],{"__ignoreMap":36},[40,1841,1842,1845,1848,1851,1854],{"class":42,"line":43},[40,1843,1844],{"class":147},"node",[40,1846,1847],{"class":69}," to-elementor.mjs",[40,1849,1850],{"class":69}," pricing.html",[40,1852,1853],{"class":69}," pricing.css",[40,1855,1856],{"class":69}," pricing.json\n",[11,1858,1859,1860,1302,1863,1302,1866,448,1869,1872],{},"The scoping walks the stylesheet by matching braces. It handles plain rules, ",[15,1861,1862],{},"@media",[15,1864,1865],{},"@supports",[15,1867,1868],{},"@keyframes",[15,1870,1871],{},"@font-face",". It will not survive native CSS nesting, or a comment or string that contains a brace. For a stylesheet like that, parse it properly with PostCSS.",[23,1874,1876],{"id":1875},"import-it","Import it",[1878,1879,1880,1891,1902],"ol",{},[423,1881,1882,1883,1887,1888,578],{},"In WordPress, open ",[1884,1885,1886],"strong",{},"Templates",", then ",[1884,1889,1890],{},"Saved Templates",[423,1892,1893,1894,1897,1898,1901],{},"Click ",[1884,1895,1896],{},"Import Templates"," and choose the ",[15,1899,1900],{},".json"," file.",[423,1903,1904,1905,1908],{},"Edit a page with Elementor, click the folder icon, open ",[1884,1906,1907],{},"My Templates"," and insert it.",[23,1910,1912],{"id":1911},"five-things-that-break-after-import","Five things that break after import",[11,1914,1915],{},"These are the real ones. All five came up while building an exporter that does this for arbitrary websites.",[11,1917,1918,1921,1922,1925,1926,1929,1930,1302,1933,1302,1936,1939,1940,1943],{},[1884,1919,1920],{},"1. Relative URLs."," ",[15,1923,1924],{},"src=\"/img/hero.png\""," now points at your WordPress site. A widget cannot carry a ",[15,1927,1928],{},"\u003Cbase>"," tag, so every ",[15,1931,1932],{},"src",[15,1934,1935],{},"href",[15,1937,1938],{},"srcset"," and CSS ",[15,1941,1942],{},"url()"," has to be absolute before you wrap it.",[11,1945,1946,1949,1950,1302,1953,1302,1956,448,1958,1961,1962,1302,1965,1968],{},[1884,1947,1948],{},"2. The theme leaks in."," Your theme styles ",[15,1951,1952],{},"h1",[15,1954,1955],{},"a",[15,1957,420],{},[15,1959,1960],{},"button",". If the original page relied on a CSS reset (Tailwind's Preflight, normalize.css), that reset is gone and headings get the theme's margins and colors. Add a small reset under your scope class: zero margins, ",[15,1963,1964],{},"list-style: none",[15,1966,1967],{},"color: inherit"," on links and headings.",[11,1970,1971,1974],{},[1884,1972,1973],{},"3. The block is squeezed."," Even a full-width container sits inside the theme's content column on many themes. Break out with:",[31,1976,1979],{"className":1977,"code":1978,"language":774,"meta":36,"style":36},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight",".imp-abc123 { position: relative; width: 100vw; left: 50%; margin-left: -50vw; }\nhtml, body { overflow-x: clip; }\n",[15,1980,1981,2034],{"__ignoreMap":36},[40,1982,1983,1985,1988,1990,1994,1996,1999,2002,2005,2007,2010,2012,2014,2016,2019,2021,2024,2026,2029,2031],{"class":42,"line":43},[40,1984,578],{"class":46},[40,1986,1987],{"class":147},"imp-abc123",[40,1989,197],{"class":46},[40,1991,1993],{"class":1992},"sqsOY"," position",[40,1995,63],{"class":46},[40,1997,1998],{"class":329}," relative",[40,2000,2001],{"class":46},";",[40,2003,2004],{"class":1992}," width",[40,2006,63],{"class":46},[40,2008,2009],{"class":202}," 100vw",[40,2011,2001],{"class":46},[40,2013,1627],{"class":1992},[40,2015,63],{"class":46},[40,2017,2018],{"class":202}," 50%",[40,2020,2001],{"class":46},[40,2022,2023],{"class":1992}," margin-left",[40,2025,63],{"class":46},[40,2027,2028],{"class":202}," -50vw",[40,2030,2001],{"class":46},[40,2032,2033],{"class":46}," }\n",[40,2035,2036,2038,2040,2042,2044,2047,2049,2052,2054],{"class":42,"line":50},[40,2037,295],{"class":147},[40,2039,490],{"class":46},[40,2041,1064],{"class":147},[40,2043,197],{"class":46},[40,2045,2046],{"class":1992}," overflow-x",[40,2048,63],{"class":46},[40,2050,2051],{"class":329}," clip",[40,2053,2001],{"class":46},[40,2055,2033],{"class":46},[11,2057,2058,2059,2062,2063,2066,2067,2069],{},"Use ",[15,2060,2061],{},"clip",", not ",[15,2064,2065],{},"hidden",". ",[15,2068,2065],{}," creates a scroll container and breaks sticky headers.",[11,2071,2072,2075,2076,2079],{},[1884,2073,2074],{},"4. Everything is invisible."," Sites that fade sections in on scroll ship the starting state in CSS (",[15,2077,2078],{},"opacity: 0; transform: translateY(28px)",") and remove it with JavaScript. The JavaScript did not come along. Delete those declarations or the section never appears.",[11,2081,2082,2085,2086,2089],{},[1884,2083,2084],{},"5. A byte order mark."," If your editor saves the file as UTF-8 with BOM, PHP's ",[15,2087,2088],{},"json_decode"," rejects it and Elementor reports an invalid file. Save without one.",[23,2091,2093],{"id":2092},"when-this-is-the-wrong-approach","When this is the wrong approach",[11,2095,2096],{},"If the client needs to change a headline from Elementor's sidebar, do not import an HTML widget. Rebuild the section with native widgets. It takes longer and it is the right answer for content that non-developers will edit every week.",[11,2098,2099],{},"The HTML widget route is for sections that should look exactly like the source and change rarely: a hero from the old site during a migration, a pricing table, a footer.",[23,2101,2103],{"id":2102},"the-four-ways-compared","The four ways, compared",[2105,2106,2107,2126],"table",{},[2108,2109,2110],"thead",{},[2111,2112,2113,2117,2120,2123],"tr",{},[2114,2115,2116],"th",{},"Method",[2114,2118,2119],{},"Time per section",[2114,2121,2122],{},"Handles the five problems",[2114,2124,2125],{},"Good for",[2127,2128,2129,2144,2158,2172],"tbody",{},[2111,2130,2131,2135,2138,2141],{},[2132,2133,2134],"td",{},"By hand in the HTML widget",[2132,2136,2137],{},"20 to 40 minutes",[2132,2139,2140],{},"You do",[2132,2142,2143],{},"One section, once",[2111,2145,2146,2149,2152,2155],{},[2132,2147,2148],{},"The script above",[2132,2150,2151],{},"2 minutes, after you have clean HTML and CSS",[2132,2153,2154],{},"Scoping only",[2132,2156,2157],{},"HTML you already own",[2111,2159,2160,2163,2166,2169],{},[2132,2161,2162],{},"A browser extension",[2132,2164,2165],{},"1 minute",[2132,2167,2168],{},"Yes",[2132,2170,2171],{},"Picking sections visually",[2111,2173,2174,2177,2180,2182],{},[2132,2175,2176],{},"An API call",[2132,2178,2179],{},"Seconds",[2132,2181,2168],{},[2132,2183,2184],{},"Many sections, pipelines, AI agents",[11,2186,2187],{},"The catch with the script is the input. It needs the section's HTML and only the CSS that applies to it. On a real site that CSS is spread over several stylesheets and thousands of rules, and extracting the right ones is most of the work.",[23,2189,2191],{"id":2190},"or-use-one-api-call","Or use one API call",[11,2193,2194,2195,63],{},"MiroMiro's code endpoint resolves which CSS rules apply to a section of a live page and returns the finished template. Pass ",[15,2196,2197],{},"framework=elementor",[31,2199,2201],{"className":1835,"code":2200,"language":1837,"meta":36,"style":36},"curl -s \"https://miromiro.app/api/v1/code?url=stripe.com&section=pricing&framework=elementor\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\" \\\n  | jq -r .code > pricing.json\n",[15,2202,2203,2221,2238],{"__ignoreMap":36},[40,2204,2205,2208,2211,2213,2216,2218],{"class":42,"line":43},[40,2206,2207],{"class":147},"curl",[40,2209,2210],{"class":69}," -s",[40,2212,66],{"class":46},[40,2214,2215],{"class":69},"https://miromiro.app/api/v1/code?url=stripe.com&section=pricing&framework=elementor",[40,2217,60],{"class":46},[40,2219,2220],{"class":329}," \\\n",[40,2222,2223,2226,2228,2231,2234,2236],{"class":42,"line":50},[40,2224,2225],{"class":69},"  -H",[40,2227,66],{"class":46},[40,2229,2230],{"class":69},"Authorization: Bearer ",[40,2232,2233],{"class":329},"$MIROMIRO_API_KEY",[40,2235,60],{"class":46},[40,2237,2220],{"class":329},[40,2239,2240,2243,2246,2249,2252,2255],{"class":42,"line":78},[40,2241,2242],{"class":46},"  |",[40,2244,2245],{"class":147}," jq",[40,2247,2248],{"class":69}," -r",[40,2250,2251],{"class":69}," .code",[40,2253,2254],{"class":46}," >",[40,2256,1856],{"class":69},[11,2258,2259,2262,2263,2266],{},[15,2260,2261],{},"pricing.json"," is ready to import. It has absolute URLs, scoped CSS with the reset, the full-width breakout, responsive and hover rules, and scroll-reveal states already switched to visible. ",[15,2264,2265],{},"framework=shopify"," returns a theme section instead.",[11,2268,2269],{},"One limit to know: it is still one HTML widget, and a section the engine reads badly imports just as badly. Check the result in a staging site before you put it in front of a client.",[11,2271,2272,2273,2277],{},"If you work in ChatGPT or Claude, the same thing is available as a tool: connect the ",[1955,2274,2276],{"href":2275},"/api/mcp","MCP server"," and ask for \"the pricing section of stripe.com as an Elementor template\".",[11,2279,2280,2281,2285],{},"You can ",[1955,2282,2284],{"href":2283},"/api/onboarding","start for free"," - 300 credits a month, no card. A section costs 25.",[23,2287,2289],{"id":2288},"summary","Summary",[11,2291,2292],{},"Use the HTML widget inside a full-width container, scope your CSS, and fix URLs before you wrap anything. Write the script yourself if the HTML is yours. Use an extractor when the section lives on a site whose CSS you would otherwise have to untangle by hand.",[23,2294,2296],{"id":2295},"frequently-asked-questions","Frequently asked questions",[2298,2299,2301],"h3",{"id":2300},"what-format-is-an-elementor-template-file","What format is an Elementor template file?",[11,2303,2304,2305,1302,2307,1302,2309,2311,2312,2314,2315,2317,2318,2320,2321,2323,2324,578],{},"JSON, with ",[15,2306,57],{},[15,2308,104],{},[15,2310,83],{}," and a ",[15,2313,125],{}," array of elements. Each element has an ",[15,2316,148],{},", an ",[15,2319,169],{}," and its own ",[15,2322,225],{}," children. Widgets add a ",[15,2325,286],{},[2298,2327,2329],{"id":2328},"can-i-convert-html-to-native-elementor-widgets-automatically","Can I convert HTML to native Elementor widgets automatically?",[11,2331,2332],{},"Not reliably. The original CSS targets the original markup, so remapping it onto heading and button widgets breaks the layout. One HTML widget keeps it intact.",[2298,2334,2336],{"id":2335},"why-does-my-imported-html-look-squeezed","Why does my imported HTML look squeezed?",[11,2338,2339,2340,2342,2343,2345,2346,2349],{},"The theme's content column. Set the container's ",[15,2341,203],{}," to ",[15,2344,212],{},", and if the theme still constrains it, use the ",[15,2347,2348],{},"100vw"," breakout above.",[2298,2351,2353],{"id":2352},"does-the-html-widget-need-elementor-pro","Does the HTML widget need Elementor Pro?",[11,2355,2356],{},"No. The HTML widget, containers and template import are in the free plugin.",[2298,2358,2360],{"id":2359},"can-i-do-the-same-for-shopify","Can I do the same for Shopify?",[11,2362,2363,2364,2367,2368,2370,2371,2374,2375,2378],{},"Yes. The equivalent is a section file in the theme's ",[15,2365,2366],{},"sections"," folder: the markup and a ",[15,2369,458],{}," block inside ",[15,2372,2373],{},"{% raw %}",", followed by a ",[15,2376,2377],{},"{% schema %}"," block with a name and a preset.",[2380,2381,2382],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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);}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}",{"title":36,"searchDepth":50,"depth":50,"links":2384},[2385,2386,2387,2388,2389,2390,2391,2392,2393],{"id":25,"depth":50,"text":26},{"id":462,"depth":50,"text":463},{"id":1875,"depth":50,"text":1876},{"id":1911,"depth":50,"text":1912},{"id":2092,"depth":50,"text":2093},{"id":2102,"depth":50,"text":2103},{"id":2190,"depth":50,"text":2191},{"id":2288,"depth":50,"text":2289},{"id":2295,"depth":50,"text":2296,"children":2394},[2395,2396,2397,2398,2399],{"id":2300,"depth":78,"text":2301},{"id":2328,"depth":78,"text":2329},{"id":2335,"depth":78,"text":2336},{"id":2352,"depth":78,"text":2353},{"id":2359,"depth":78,"text":2360},"Tutorials","2026-10-12","The Elementor template format explained, a short Node script that wraps any HTML and CSS into an importable .json file, the five things that break on import, and the one-call API version.","md",[2405,2407,2409,2412,2415],{"question":2301,"answer":2406},"A JSON file with four top-level keys that matter: version (0.4), type (section or page), title, and content. content is an array of elements. Each element has an id, an elType (container or widget), a settings object and an elements array of children. A widget also has a widgetType, such as html or heading.",{"question":2329,"answer":2408},"Not reliably. Arbitrary HTML does not map onto Elementor's heading, image and button widgets without breaking the layout, because the original CSS targets the original markup. The dependable conversion puts the HTML and its CSS inside one HTML widget. It looks identical and you edit it as code.",{"question":2410,"answer":2411},"How do I import a JSON template into Elementor?","In WordPress go to Templates, then Saved Templates, and click Import Templates. Choose the .json file. The template appears in your library, and you insert it into any page from the editor's folder icon under My Templates.",{"question":2413,"answer":2414},"Why does my imported HTML look squeezed in Elementor?","The widget sits inside your theme's content column, which is often around 650 to 1140 pixels wide. Use a container with content_width set to full, or break the block out with width: 100vw and a negative margin, so the section spans the page like the original.",{"question":2353,"answer":2416},"No. The HTML widget, containers and template import are all in the free plugin.",null,{},"/api-blog/convert-html-to-elementor-json-template",[2421,2422,2423,2424],"best-design-to-code-apis-2026","connect-miromiro-mcp-claude-cursor-chatgpt","how-to-extract-design-tokens-from-any-website-in-python","how-to-extract-brand-assets-in-bulk",{"title":5,"description":2402},"Convert HTML to an Elementor JSON Template: Format + Script","api-blog/convert-html-to-elementor-json-template",[2429,2430,2431,2432,2433,2434],"elementor","wordpress","html to elementor","json template","page builders","api","3oenPSPWl6sQaHzWTec870DwTA4gyMeayrziIjsUsus",[2437,2832,3676],{"id":2438,"title":2439,"author":6,"body":2440,"category":2806,"date":2807,"dateModified":2417,"description":2808,"extension":2403,"faqs":2809,"image":2417,"meta":2820,"navigation":533,"path":2821,"readingTime":141,"relatedSlugs":2822,"seo":2824,"seoTitle":2417,"stem":2825,"tags":2826,"__hash__":2831},"apiBlog/api-blog/best-design-to-code-apis-2026.md","Best Design-to-Code APIs and Tools in 2026",{"type":8,"value":2441,"toc":2792},[2442,2445,2448,2452,2478,2498,2505,2509,2519,2536,2540,2550,2567,2571,2579,2610,2617,2638,2642,2744,2748,2759,2761,2765,2768,2772,2775,2779,2782,2786,2789],[11,2443,2444],{},"\"Design to code\" means four different products depending on what goes in: a Figma file, a screenshot, a prompt, or a live URL. Most listicles mix them together and compare a Figma plugin against an AI app builder, which helps nobody. Here is the landscape sorted by input, with an honest read on each.",[11,2446,2447],{},"Disclosure up front: we build the URL-to-code entry. The others are genuinely good at their own input type, and I will say so.",[23,2449,2451],{"id":2450},"input-a-figma-file","Input: a Figma file",[11,2453,2454,448,2462,2469,2470,2477],{},[1884,2455,2456],{},[1955,2457,2461],{"href":2458,"rel":2459},"https://www.animaapp.com",[2460],"nofollow","Anima",[1884,2463,2464],{},[1955,2465,2468],{"href":2466,"rel":2467},"https://www.locofy.ai",[2460],"Locofy"," are the established Figma-to-code plugins - React/HTML output, component mapping, years of production use. ",[1884,2471,2472],{},[1955,2473,2476],{"href":2474,"rel":2475},"https://www.builder.io",[2460],"Builder.io's Visual Copilot"," is the newer AI take on the same job.",[420,2479,2480,2486,2492],{},[423,2481,2482,2485],{},[1884,2483,2484],{},"Good:"," if your designs live in Figma with proper auto-layout, these produce real component code and preserve design intent.",[423,2487,2488,2491],{},[1884,2489,2490],{},"Bad:"," they need a Figma file. Messy layers produce messy code, and there is no path from \"site I saw\" to code - someone has to design it first.",[423,2493,2494,2497],{},[1884,2495,2496],{},"Use when:"," your team designs in Figma and wants the handoff automated.",[11,2499,2500,2501,578],{},"Coding with an agent rather than a plugin? Figma's own MCP server gives Claude Code or Cursor the selected frame directly - see ",[1955,2502,2504],{"href":2503},"/api/blog/figma-mcp-setup-and-alternatives","Figma MCP setup, and what to use without a Figma file",[23,2506,2508],{"id":2507},"input-a-screenshot","Input: a screenshot",[11,2510,2511,2518],{},[1884,2512,2513],{},[1955,2514,2517],{"href":2515,"rel":2516},"https://github.com/abi/screenshot-to-code",[2460],"screenshot-to-code"," (open source, very popular) and the screenshot modes of various AI tools take a picture and ask a vision model to write matching HTML/Tailwind.",[420,2520,2521,2526,2531],{},[423,2522,2523,2525],{},[1884,2524,2484],{}," works on anything you can see. Zero setup for the OSS version. Genuinely impressive first drafts.",[423,2527,2528,2530],{},[1884,2529,2490],{}," every value is a guess. The model approximates colors from pixels, eyeballs spacing, and invents font stacks. Two runs give two different answers. Fine for a prototype, dangerous as a source of truth.",[423,2532,2533,2535],{},[1884,2534,2496],{}," speed matters more than fidelity.",[23,2537,2539],{"id":2538},"input-a-prompt","Input: a prompt",[11,2541,2542,2549],{},[1884,2543,2544],{},[1955,2545,2548],{"href":2546,"rel":2547},"https://v0.dev",[2460],"v0"," (Vercel) and its peers generate UI from a text description. Strictly speaking that is prompt-to-code, not design-to-code, but it is where much of the category's attention went.",[420,2551,2552,2557,2562],{},[423,2553,2554,2556],{},[1884,2555,2484],{}," the fastest way to get a decent generic component that never existed before.",[423,2558,2559,2561],{},[1884,2560,2490],{}," the output looks like v0 output. If the goal is \"match this existing brand\", a prompt cannot carry a design system's worth of values.",[423,2563,2564,2566],{},[1884,2565,2496],{}," greenfield UI with no reference to match.",[23,2568,2570],{"id":2569},"input-a-live-url","Input: a live URL",[11,2572,2573,2574,2578],{},"This is the gap. When the reference already exists as a website - a client's current site, a competitor's pricing page, a section your PM screenshotted - none of the above reads the real CSS. Our ",[1955,2575,2577],{"href":2576},"/api/docs/code","/v1/code"," endpoint does:",[31,2580,2582],{"className":1835,"code":2581,"language":1837,"meta":36,"style":36},"curl \"https://miromiro.app/api/v1/code?url=stripe.com&format=tailwind\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[15,2583,2584,2597],{"__ignoreMap":36},[40,2585,2586,2588,2590,2593,2595],{"class":42,"line":43},[40,2587,2207],{"class":147},[40,2589,66],{"class":46},[40,2591,2592],{"class":69},"https://miromiro.app/api/v1/code?url=stripe.com&format=tailwind",[40,2594,60],{"class":46},[40,2596,2220],{"class":329},[40,2598,2599,2601,2603,2605,2607],{"class":42,"line":50},[40,2600,2225],{"class":69},[40,2602,66],{"class":46},[40,2604,2230],{"class":69},[40,2606,2233],{"class":329},[40,2608,2609],{"class":46},"\"\n",[11,2611,2612,2613,2616],{},"It parses the live page's actual cascade - specificity, media queries, nested ",[15,2614,2615],{},"var()"," chains - and returns the section as Tailwind, JSX, Vue, or plain HTML, with the extracted token set alongside. Auto-detects the hero if you do not pass a selector; 25 credits a call.",[420,2618,2619,2624,2633],{},[423,2620,2621,2623],{},[1884,2622,2484],{}," the values are real, not guessed - the exact colors, spacing, and fonts the site ships. It is an API, so it runs inside agents and products, which almost nothing else in this category does.",[423,2625,2626,2628,2629,2632],{},[1884,2627,2490],{}," section-level, not whole-app cloning - it will not produce a working multi-page app from a URL. Static HTML + CSS by default; add ",[15,2630,2631],{},"render=true"," (+5 credits) for fully client-rendered apps - it loads the page in a real browser and runs its JavaScript first.",[423,2634,2635,2637],{},[1884,2636,2496],{}," an agent or product needs faithful code from a page that already exists.",[23,2639,2641],{"id":2640},"the-whole-landscape-in-one-table","The whole landscape in one table",[2105,2643,2644,2663],{},[2108,2645,2646],{},[2111,2647,2648,2651,2654,2657,2660],{},[2114,2649,2650],{},"Tool",[2114,2652,2653],{},"Input",[2114,2655,2656],{},"Output",[2114,2658,2659],{},"API available",[2114,2661,2662],{},"Values real or guessed?",[2127,2664,2665,2682,2697,2713,2728],{},[2111,2666,2667,2670,2673,2676,2679],{},[2132,2668,2669],{},"Anima / Locofy",[2132,2671,2672],{},"Figma file",[2132,2674,2675],{},"React/HTML components",[2132,2677,2678],{},"Limited",[2132,2680,2681],{},"Real (from Figma)",[2111,2683,2684,2687,2689,2692,2695],{},[2132,2685,2686],{},"Builder.io Visual Copilot",[2132,2688,2672],{},[2132,2690,2691],{},"Components",[2132,2693,2694],{},"Yes (Builder ecosystem)",[2132,2696,2681],{},[2111,2698,2699,2701,2704,2707,2710],{},[2132,2700,2517],{},[2132,2702,2703],{},"Image",[2132,2705,2706],{},"HTML/Tailwind draft",[2132,2708,2709],{},"Self-host",[2132,2711,2712],{},"Guessed",[2111,2714,2715,2717,2720,2723,2725],{},[2132,2716,2548],{},[2132,2718,2719],{},"Prompt",[2132,2721,2722],{},"React/Tailwind",[2132,2724,2168],{},[2132,2726,2727],{},"Invented",[2111,2729,2730,2733,2736,2739,2741],{},[2132,2731,2732],{},"MiroMiro /v1/code",[2132,2734,2735],{},"Live URL",[2132,2737,2738],{},"Tailwind/JSX/Vue + tokens",[2132,2740,2168],{},[2132,2742,2743],{},"Real (from the site's CSS)",[23,2745,2747],{"id":2746},"the-verdict","The verdict",[11,2749,2750,2751,2754,2755,578],{},"Match the tool to the input you actually have. Figma file: Anima or Locofy. Nothing yet: v0. A picture and low stakes: screenshot-to-code. A live site that must be matched faithfully, inside a pipeline: that is URL-to-code, and it is the lane we picked precisely because the others do not drive in it. ",[1955,2752,2753],{"href":2283},"Free key"," (300 credits a month, no card) or try a URL in the ",[1955,2756,2758],{"href":2757},"/api/demo","playground",[23,2760,2296],{"id":2295},[2298,2762,2764],{"id":2763},"what-is-the-best-way-to-turn-an-existing-website-into-code","What is the best way to turn an existing website into code?",[11,2766,2767],{},"If fidelity matters, read the live CSS (URL-to-code). If speed matters more, screenshot-to-code gives a fast approximation.",[2298,2769,2771],{"id":2770},"can-ai-convert-a-screenshot-to-code-accurately","Can AI convert a screenshot to code accurately?",[11,2773,2774],{},"Plausibly, not accurately. Every color, spacing, and font value is a model's guess from pixels.",[2298,2776,2778],{"id":2777},"figma-to-code-vs-url-to-code","Figma-to-code vs URL-to-code?",[11,2780,2781],{},"Input: a design file you own vs any live website. The first automates handoff; the second works when no design file exists.",[2298,2783,2785],{"id":2784},"which-of-these-have-real-apis","Which of these have real APIs?",[11,2787,2788],{},"v0 and Builder.io within their platforms, and MiroMiro as a plain REST endpoint. The Figma plugins are primarily apps - a hard filter if you are building a pipeline.",[2380,2790,2791],{},"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":36,"searchDepth":50,"depth":50,"links":2793},[2794,2795,2796,2797,2798,2799,2800],{"id":2450,"depth":50,"text":2451},{"id":2507,"depth":50,"text":2508},{"id":2538,"depth":50,"text":2539},{"id":2569,"depth":50,"text":2570},{"id":2640,"depth":50,"text":2641},{"id":2746,"depth":50,"text":2747},{"id":2295,"depth":50,"text":2296,"children":2801},[2802,2803,2804,2805],{"id":2763,"depth":78,"text":2764},{"id":2770,"depth":78,"text":2771},{"id":2777,"depth":78,"text":2778},{"id":2784,"depth":78,"text":2785},"Comparisons","2026-07-18","The design-to-code landscape in 2026 sorted by input - Figma files, screenshots, prompts, or live URLs - and which tool honestly wins for each job.",[2810,2812,2814,2817],{"question":2764,"answer":2811},"Depends on fidelity needs. For a quick approximation, screenshot-to-code tools work from a picture. For code that uses the site's real CSS values - exact colors, spacing, fonts - you need something that reads the live page's stylesheets, which is what a URL-to-code API does.",{"question":2771,"answer":2813},"It produces plausible code fast, but the model guesses every value: colors approximated from pixels, spacing eyeballed, fonts inferred. Great for prototypes; risky when the output must match a real design system.",{"question":2815,"answer":2816},"What is the difference between Figma-to-code and URL-to-code?","Input. Figma-to-code tools (Anima, Locofy, Builder.io) need a Figma file and turn design layers into components. URL-to-code starts from any live website - no design file required - and derives the code from the real rendered CSS.",{"question":2818,"answer":2819},"Which design-to-code tools have an actual API?","Fewer than the category suggests. Most are plugins or apps. If you need code generation inside your own product or agent pipeline, check for a documented REST endpoint first - that requirement alone eliminates most of the list.",{},"/api-blog/best-design-to-code-apis-2026",[2823,2423],"best-design-token-extraction-tools-2026",{"title":2439,"description":2808},"api-blog/best-design-to-code-apis-2026",[2827,2828,2829,2830],"design to code","tailwind","code generation","comparison","-4EJX91M1I-B-as9PjvxqwKIN6n1Iwp44ktl9vT1WtE",{"id":2833,"title":2834,"author":6,"body":2835,"category":3646,"date":3647,"dateModified":3648,"description":3649,"extension":2403,"faqs":3650,"image":2417,"meta":3662,"navigation":533,"path":3663,"readingTime":135,"relatedSlugs":3664,"seo":3667,"seoTitle":3668,"stem":3669,"tags":3670,"__hash__":3675},"apiBlog/api-blog/connect-miromiro-mcp-claude-cursor-chatgpt.md","How to Connect the MiroMiro MCP to Claude, Cursor, ChatGPT and Codex",{"type":8,"value":2836,"toc":3627},[2837,2844,2851,2855,2858,2910,2917,2923,2932,2936,2948,3046,3052,3056,3059,3132,3138,3141,3144,3152,3156,3162,3179,3194,3204,3208,3211,3239,3245,3297,3305,3309,3312,3370,3390,3394,3397,3466,3469,3473,3476,3490,3494,3533,3537,3543,3547,3550,3580,3587,3589,3593,3596,3600,3603,3607,3610,3614,3624],[11,2838,2839,2840,2843],{},"Every client that speaks MCP can connect to MiroMiro with one URL. Paste ",[15,2841,2842],{},"https://miromiro.app/mcp"," into the client's connector settings and nine design-extraction tools appear in your chats: section-to-code, brand, design tokens, images, SVGs, fonts, videos, audio and Lottie.",[11,2845,2846,2847,2850],{},"You do not need to paste a key. The bare URL triggers a one-click sign in with your MiroMiro account - ",[1955,2848,2849],{"href":2283},"free",", 300 credits a month, no card. Client-by-client steps below.",[23,2852,2854],{"id":2853},"claude-web-and-desktop","Claude - web and desktop",[11,2856,2857],{},"Claude calls these custom connectors. Two ways in:",[1878,2859,2860,2878,2898,2901],{},[423,2861,2862,2863,2866,2867,2870,2871,2870,2874,2877],{},"In any chat, click the ",[1884,2864,2865],{},"+"," button in the input, then ",[1884,2868,2869],{},"Connectors"," → ",[1884,2872,2873],{},"Add connector",[1884,2875,2876],{},"Add custom connector",". (Settings → Connectors gets you to the same dialog.)",[423,2879,2880,2881],{},"In the dialog, fill in:\n",[420,2882,2883,2891],{},[423,2884,2885,1921,2888],{},[1884,2886,2887],{},"Name:",[15,2889,2890],{},"MiroMiro",[423,2892,2893,1921,2896],{},[1884,2894,2895],{},"URL:",[15,2897,2842],{},[423,2899,2900],{},"Leave the OAuth Client ID and Secret fields under Advanced settings empty - Claude discovers them from the server.",[423,2902,1893,2903,2906,2907,578],{},[1884,2904,2905],{},"Add",". Claude opens the MiroMiro sign-in page; approve it and the tools are live in every chat - ask \"get the brand colors and fonts of stripe.com\" and watch it call ",[15,2908,2909],{},"extract_brand",[11,2911,2912],{},[2913,2914],"img",{"alt":2915,"src":2916},"Claude's + menu: Connectors, then Add connector, then Add custom connector","/api-blog/connector1.png",[11,2918,2919],{},[2913,2920],{"alt":2921,"src":2922},"Claude's Add custom connector dialog with the MiroMiro name and URL filled in","/api-blog/connector2.png",[11,2924,2925,2926,2931],{},"One click faster: ",[1955,2927,2930],{"href":2928,"rel":2929},"https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=MiroMiro&connectorUrl=https%3A%2F%2Fmiromiro.app%2Fmcp",[2460],"Add MiroMiro to Claude"," opens this dialog with the name and URL pre-filled - you just click Add and sign in.",[23,2933,2935],{"id":2934},"cursor","Cursor",[11,2937,2938,2939,2944,2945,63],{},"Cursor supports install deeplinks, so ",[1955,2940,2943],{"href":2941,"rel":2942},"https://cursor.com/install-mcp?name=miromiro&config=eyJ1cmwiOiJodHRwczovL21pcm9taXJvLmFwcC9tY3AifQ%3D%3D",[2460],"Add MiroMiro to Cursor"," does the whole thing in one click - it opens Cursor with the server config pre-filled. Manual version, in ",[15,2946,2947],{},"~/.cursor/mcp.json",[31,2949,2951],{"className":33,"code":2950,"language":35,"meta":36,"style":36},"{\n  \"mcpServers\": {\n    \"miromiro\": {\n      \"url\": \"https://miromiro.app/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer mm_live_your_key\" }\n    }\n  }\n}\n",[15,2952,2953,2957,2970,2984,3003,3034,3038,3042],{"__ignoreMap":36},[40,2954,2955],{"class":42,"line":43},[40,2956,47],{"class":46},[40,2958,2959,2961,2964,2966,2968],{"class":42,"line":50},[40,2960,53],{"class":46},[40,2962,2963],{"class":56},"mcpServers",[40,2965,60],{"class":46},[40,2967,63],{"class":46},[40,2969,779],{"class":46},[40,2971,2972,2975,2978,2980,2982],{"class":42,"line":78},[40,2973,2974],{"class":46},"    \"",[40,2976,2977],{"class":147},"miromiro",[40,2979,60],{"class":46},[40,2981,63],{"class":46},[40,2983,779],{"class":46},[40,2985,2986,2988,2991,2993,2995,2997,2999,3001],{"class":42,"line":99},[40,2987,144],{"class":46},[40,2989,2990],{"class":202},"url",[40,2992,60],{"class":46},[40,2994,63],{"class":46},[40,2996,66],{"class":46},[40,2998,2842],{"class":69},[40,3000,60],{"class":46},[40,3002,75],{"class":46},[40,3004,3005,3007,3010,3012,3014,3016,3018,3021,3023,3025,3027,3030,3032],{"class":42,"line":120},[40,3006,144],{"class":46},[40,3008,3009],{"class":202},"headers",[40,3011,60],{"class":46},[40,3013,63],{"class":46},[40,3015,197],{"class":46},[40,3017,66],{"class":46},[40,3019,3020],{"class":317},"Authorization",[40,3022,60],{"class":46},[40,3024,63],{"class":46},[40,3026,66],{"class":46},[40,3028,3029],{"class":69},"Bearer mm_live_your_key",[40,3031,60],{"class":46},[40,3033,2033],{"class":46},[40,3035,3036],{"class":42,"line":135},[40,3037,389],{"class":46},[40,3039,3040],{"class":42,"line":141},[40,3041,1340],{"class":46},[40,3043,3044],{"class":42,"line":164},[40,3045,415],{"class":46},[11,3047,3048,3049,3051],{},"Drop the ",[15,3050,3009],{}," line and Cursor prompts you to sign in instead - either works. If you do use a key, the header keeps it out of logs. Restart Cursor and check the MCP panel for the nine tools.",[23,3053,3055],{"id":3054},"chatgpt","ChatGPT",[11,3057,3058],{},"ChatGPT only takes remote servers, which is exactly what this is:",[1878,3060,3061,3073,3119],{},[423,3062,3063,2870,3066,3069,3070],{},[1884,3064,3065],{},"Settings",[1884,3067,3068],{},"Security and login"," → enable ",[1884,3071,3072],{},"Developer mode",[423,3074,3075,2870,3077,3080,3081,3086,3087,3090,3091],{},[1884,3076,3065],{},[1884,3078,3079],{},"Plugins"," (or go straight to ",[1955,3082,3085],{"href":3083,"rel":3084},"https://chatgpt.com/plugins",[2460],"chatgpt.com/plugins",") → ",[1884,3088,3089],{},"New Plugin",":\n",[420,3092,3093,3099,3107,3113],{},[423,3094,3095,1921,3097],{},[1884,3096,2887],{},[15,3098,2890],{},[423,3100,3101,3104,3105],{},[1884,3102,3103],{},"Connection:"," Server URL, ",[15,3106,2842],{},[423,3108,3109,3112],{},[1884,3110,3111],{},"Authentication:"," OAuth - ChatGPT reads the sign-in settings from the server, so there is nothing to configure",[423,3114,3115,3116],{},"Tick the custom-server acknowledgment and click ",[1884,3117,3118],{},"Create",[423,3120,3121,3122,3125,3126,3128,3129,578],{},"ChatGPT shows a consent card. Click ",[1884,3123,3124],{},"Sign in with MiroMiro",", approve, and the tools appear in any chat via the ",[1884,3127,2865],{}," menu → ",[1884,3130,3131],{},"More",[11,3133,3134],{},[2913,3135],{"alt":3136,"src":3137},"ChatGPT's Add MiroMiro to ChatGPT consent screen with the Sign in with MiroMiro button","/api-blog/chatgpt-consent.png",[11,3139,3140],{},"OpenAI has renamed this area twice recently - if your build still says Apps & Connectors, the Developer mode toggle lives under Advanced there instead.",[11,3142,3143],{},"Prefer to delegate? Paste this prompt into the assistant and let it do the guiding - ChatGPT walks you through its own settings, and Codex installs the server for real:",[31,3145,3150],{"className":3146,"code":3148,"language":3149,"meta":36},[3147],"language-text","Add the MiroMiro MCP server to this client.\n\nServer name: miromiro\nServer URL:  https://miromiro.app/mcp\nTransport:   Streamable HTTP (stateless JSON responses)\nAuth:        OAuth (auto-discovered). On first connect you'll be asked to sign in to MiroMiro - a free account includes 300 credits/month, no card. API keys also work: append ?access_key=mm_live_YOUR_KEY or send \"Authorization: Bearer\".\n\nIf you are ChatGPT: Settings → Security and login → enable Developer mode, then Settings → Plugins (chatgpt.com/plugins) → New Plugin with the URL above (authentication: OAuth).\nIf you are Codex CLI: add to ~/.codex/config.toml:\n[mcp_servers.miromiro]\nurl = \"https://miromiro.app/mcp\"\nThen run: codex mcp login miromiro (signs in through the browser).\n\nThen list the tools - you should see extract_code, extract_brand, extract_design_tokens, extract_assets, extract_svgs, extract_fonts, extract_videos, extract_audio and extract_lottie.\n","text",[15,3151,3148],{"__ignoreMap":36},[23,3153,3155],{"id":3154},"codex-cli","Codex CLI",[11,3157,3158,3159,63],{},"Add to ",[15,3160,3161],{},"~/.codex/config.toml",[31,3163,3167],{"className":3164,"code":3165,"language":3166,"meta":36,"style":36},"language-toml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[mcp_servers.miromiro]\nurl = \"https://miromiro.app/mcp\"\n","toml",[15,3168,3169,3174],{"__ignoreMap":36},[40,3170,3171],{"class":42,"line":43},[40,3172,3173],{},"[mcp_servers.miromiro]\n",[40,3175,3176],{"class":42,"line":50},[40,3177,3178],{},"url = \"https://miromiro.app/mcp\"\n",[11,3180,3181,3182,3185,3186,3189,3190,3193],{},"Then sign in once: run ",[15,3183,3184],{},"codex mcp login miromiro"," and approve in the browser. ",[15,3187,3188],{},"codex mcp list",", or ",[15,3191,3192],{},"/mcp"," inside Codex, shows it connected. The command line and the IDE extension share this file.",[11,3195,3196,3197,3200,3201,578],{},"To pin a key instead, add ",[15,3198,3199],{},"bearer_token_env_var = \"MIROMIRO_API_KEY\""," under the URL and set that environment variable, or use ",[15,3202,3203],{},"url = \"https://miromiro.app/mcp?access_key=mm_live_your_key\"",[23,3205,3207],{"id":3206},"claude-code","Claude Code",[11,3209,3210],{},"One command in your terminal, not inside a session:",[31,3212,3214],{"className":1835,"code":3213,"language":1837,"meta":36,"style":36},"claude mcp add --transport http miromiro https://miromiro.app/mcp\n",[15,3215,3216],{"__ignoreMap":36},[40,3217,3218,3221,3224,3227,3230,3233,3236],{"class":42,"line":43},[40,3219,3220],{"class":147},"claude",[40,3222,3223],{"class":69}," mcp",[40,3225,3226],{"class":69}," add",[40,3228,3229],{"class":69}," --transport",[40,3231,3232],{"class":69}," http",[40,3234,3235],{"class":69}," miromiro",[40,3237,3238],{"class":69}," https://miromiro.app/mcp\n",[11,3240,3241,3242,3244],{},"Then run ",[15,3243,3192],{}," inside a session to sign in - Claude Code opens the browser consent page once and stores the token. If you'd rather skip the browser, pass the key directly:",[31,3246,3248],{"className":1835,"code":3247,"language":1837,"meta":36,"style":36},"claude mcp add --transport http miromiro https://miromiro.app/mcp \\\n  --header \"Authorization: Bearer mm_live_your_key\"\n\nclaude mcp list   # look for \"✓ Connected\"\n",[15,3249,3250,3269,3281,3285],{"__ignoreMap":36},[40,3251,3252,3254,3256,3258,3260,3262,3264,3267],{"class":42,"line":43},[40,3253,3220],{"class":147},[40,3255,3223],{"class":69},[40,3257,3226],{"class":69},[40,3259,3229],{"class":69},[40,3261,3232],{"class":69},[40,3263,3235],{"class":69},[40,3265,3266],{"class":69}," https://miromiro.app/mcp",[40,3268,2220],{"class":329},[40,3270,3271,3274,3276,3279],{"class":42,"line":50},[40,3272,3273],{"class":69},"  --header",[40,3275,66],{"class":46},[40,3277,3278],{"class":69},"Authorization: Bearer mm_live_your_key",[40,3280,2609],{"class":46},[40,3282,3283],{"class":42,"line":78},[40,3284,534],{"emptyLinePlaceholder":533},[40,3286,3287,3289,3291,3294],{"class":42,"line":99},[40,3288,3220],{"class":147},[40,3290,3223],{"class":69},[40,3292,3293],{"class":69}," list",[40,3295,3296],{"class":749},"   # look for \"✓ Connected\"\n",[11,3298,3299,3300,3304],{},"Claude Code also runs the ",[1955,3301,3303],{"href":3302},"/api/blog/add-design-extraction-to-claude-code-mcp","local npm package"," if you prefer the key in an env var instead of a header.",[23,3306,3308],{"id":3307},"grok-bot-muse-and-openai-dots","Grok Bot, Muse and OpenAI dots",[11,3310,3311],{},"The newer agents take the same URL, each in its own way:",[420,3313,3314,3340,3360],{},[423,3315,3316,3319,3320,3323,3324,3327,3328,2870,3330,2870,3332,3335,3336],{},[1884,3317,3318],{},"Grok Bot"," has no field for a server URL in its settings. Ask the bot instead: \"Add a custom MCP server called miromiro at ",[1955,3321,2842],{"href":2842,"rel":3322},[2460],"\". Confirm, press ",[1884,3325,3326],{},"Authorize"," on the card it posts and sign in. It then shows under ",[1884,3329,3065],{},[1884,3331,3079],{},[1884,3333,3334],{},"Yours",". Grok Bot runs on a cloud computer, so servers from your Cursor settings do not carry over. ",[1955,3337,3339],{"href":3338},"/api/agents/grok-bot","Grok Bot steps",[423,3341,3342,3345,3346,3348,3349,2870,3351,2870,3353,3355,3356],{},[1884,3343,3344],{},"Muse",": ask it in a chat to add a custom connector for ",[15,3347,2842],{}," (remote streamable HTTP, OAuth), then approve the sign-in it opens. Some accounts also have ",[1884,3350,3065],{},[1884,3352,2869],{},[1884,3354,2876],{},", which takes the same URL. ",[1955,3357,3359],{"href":3358},"/api/agents/muse","Muse steps",[423,3361,3362,3365,3366],{},[1884,3363,3364],{},"OpenAI dots"," use the plugins switched on in your ChatGPT account, so the setup is the ChatGPT one above. Start with one small request to see it working. ",[1955,3367,3369],{"href":3368},"/api/agents/openai-dots","Dots steps",[11,3371,3372,3373,1302,3376,1302,3380,1302,3383,448,3387,578],{},"Each agent also has its own page with the steps drawn out: ",[1955,3374,3055],{"href":3375},"/api/agents/chatgpt",[1955,3377,3379],{"href":3378},"/api/agents/claude","Claude",[1955,3381,3207],{"href":3382},"/api/agents/claude-code",[1955,3384,3386],{"href":3385},"/api/agents/codex","Codex",[1955,3388,2935],{"href":3389},"/api/agents/cursor",[23,3391,3393],{"id":3392},"sign-in-vs-pasting-a-key","Sign-in vs pasting a key",[11,3395,3396],{},"Both end up in the same place: signing in links your account and provisions a free API key behind the scenes, so the tiers are identical either way.",[2105,3398,3399,3411],{},[2108,3400,3401],{},[2111,3402,3403,3405,3408],{},[2114,3404],{},[2114,3406,3407],{},"Free account",[2114,3409,3410],{},"Paid plans",[2127,3412,3413,3423,3434,3445,3455],{},[2111,3414,3415,3418,3421],{},[2132,3416,3417],{},"Results",[2132,3419,3420],{},"Full",[2132,3422,3420],{},[2111,3424,3425,3428,3431],{},[2132,3426,3427],{},"Rate limit",[2132,3429,3430],{},"30/min",[2132,3432,3433],{},"60 to 300/min",[2111,3435,3436,3439,3442],{},[2132,3437,3438],{},"Credits",[2132,3440,3441],{},"300/month",[2132,3443,3444],{},"5,000 to 200,000/month",[2111,3446,3447,3450,3453],{},[2132,3448,3449],{},"Usage and logs",[2132,3451,3452],{},"Dashboard",[2132,3454,3452],{},[2111,3456,3457,3460,3463],{},[2132,3458,3459],{},"Price",[2132,3461,3462],{},"Free, no card",[2132,3464,3465],{},"From €19/month",[11,3467,3468],{},"Sign-in is the better default: no key to store, tokens expire and refresh on their own, and revoking the connector kills access without touching your other keys. Paste a key only where a client cannot run the sign-in flow.",[23,3470,3472],{"id":3471},"what-to-ask-once-connected","What to ask once connected",[11,3474,3475],{},"You never name tools in prompts - ask design-shaped questions and the agent picks the right one:",[420,3477,3478,3481,3484,3487],{},[423,3479,3480],{},"\"Rebuild stripe.com's pricing section in React\"",[423,3482,3483],{},"\"Extract linear.app's design tokens and restyle our Button component to match\"",[423,3485,3486],{},"\"What fonts does vercel.com actually load?\"",[423,3488,3489],{},"\"Pull the brand palette from ourcustomer.com and generate a theme file\"",[23,3491,3493],{"id":3492},"common-problems","Common problems",[420,3495,3496,3507,3513,3523],{},[423,3497,3498,3501,3502,3506],{},[1884,3499,3500],{},"\"Invalid or revoked API key\" in the tool result."," The key in your URL or header is wrong or was rotated. Grab the current one from ",[1955,3503,3505],{"href":3504},"/api/dashboard/keys","API Keys"," - keys are shown once at creation, so rotate if you lost it.",[423,3508,3509,3512],{},[1884,3510,3511],{},"The client keeps asking you to sign in."," Remove the connector and add it again - a stale token from a revoked link is the usual cause. Tokens normally refresh on their own for 180 days.",[423,3514,3515,3518,3519,3522],{},[1884,3516,3517],{},"Thin results on an app-like site."," The engine reads static HTML and CSS by default, so a fully client-rendered app returns little - and the tools say so instead of inventing values. The fix is one argument: ",[15,3520,3521],{},"render: true"," loads the page in a real browser and runs its JavaScript before extracting (+5 credits). The thin response includes a hint telling the agent exactly that.",[423,3524,3525,3528,3529,3532],{},[1884,3526,3527],{},"Claude web has no header field."," Correct - claude.ai custom connectors cannot send custom headers. You do not need one: the sign-in flow handles auth, and ",[15,3530,3531],{},"?access_key="," still works if you prefer a pinned key.",[23,3534,3536],{"id":3535},"already-using-the-figma-mcp","Already using the Figma MCP?",[11,3538,3539,3540,578],{},"Keep it. Figma's server reads your own design files; MiroMiro reads live websites. They are both remote URLs and work side by side in the same client - the comparison and the Figma setup steps are in ",[1955,3541,3542],{"href":2503},"Figma MCP: setup and what to use without a Figma file",[23,3544,3546],{"id":3545},"or-skip-mcp-entirely","Or skip MCP entirely",[11,3548,3549],{},"The same engine is a plain REST API:",[31,3551,3553],{"className":1835,"code":3552,"language":1837,"meta":36,"style":36},"curl \"https://miromiro.app/api/v1/brand?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[15,3554,3555,3568],{"__ignoreMap":36},[40,3556,3557,3559,3561,3564,3566],{"class":42,"line":43},[40,3558,2207],{"class":147},[40,3560,66],{"class":46},[40,3562,3563],{"class":69},"https://miromiro.app/api/v1/brand?url=stripe.com",[40,3565,60],{"class":46},[40,3567,2220],{"class":329},[40,3569,3570,3572,3574,3576,3578],{"class":42,"line":50},[40,3571,2225],{"class":69},[40,3573,66],{"class":46},[40,3575,2230],{"class":69},[40,3577,2233],{"class":329},[40,3579,2609],{"class":46},[11,3581,3582,3583,3586],{},"Full reference at ",[1955,3584,3585],{"href":3585},"/api/docs",". You can start for free.",[23,3588,2296],{"id":2295},[2298,3590,3592],{"id":3591},"how-do-i-add-a-custom-mcp-connector-to-claude-web","How do I add a custom MCP connector to Claude web?",[11,3594,3595],{},"The + button in the chat input → Connectors → Add connector → Add custom connector. Name it, paste the URL, click Add, then approve the MiroMiro sign-in page it opens.",[2298,3597,3599],{"id":3598},"do-i-need-a-key-to-try-it","Do I need a key to try it?",[11,3601,3602],{},"No. Paste the bare URL and sign in with a MiroMiro account when prompted - free, 300 credits a month, no card. Keys are the fallback for clients that cannot run the sign-in flow.",[2298,3604,3606],{"id":3605},"which-clients-does-the-remote-server-work-with","Which clients does the remote server work with?",[11,3608,3609],{},"Claude web and desktop, Cursor, ChatGPT developer mode, Codex, Claude Code, Grok Bot, Muse, Windsurf, and any MCP SDK. If it takes a server URL, it works.",[2298,3611,3613],{"id":3612},"where-does-my-key-go-safest","Where does my key go safest?",[11,3615,3616,3617,3620,3621,3623],{},"Header (",[15,3618,3619],{},"Authorization: Bearer",") where the client supports it - Cursor, Claude Code, SDKs. URL (",[15,3622,3531],{},") only where headers are not an option - claude.ai, ChatGPT. Rotate keys anytime in the dashboard.",[2380,3625,3626],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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}",{"title":36,"searchDepth":50,"depth":50,"links":3628},[3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640],{"id":2853,"depth":50,"text":2854},{"id":2934,"depth":50,"text":2935},{"id":3054,"depth":50,"text":3055},{"id":3154,"depth":50,"text":3155},{"id":3206,"depth":50,"text":3207},{"id":3307,"depth":50,"text":3308},{"id":3392,"depth":50,"text":3393},{"id":3471,"depth":50,"text":3472},{"id":3492,"depth":50,"text":3493},{"id":3535,"depth":50,"text":3536},{"id":3545,"depth":50,"text":3546},{"id":2295,"depth":50,"text":2296,"children":3641},[3642,3643,3644,3645],{"id":3591,"depth":78,"text":3592},{"id":3598,"depth":78,"text":3599},{"id":3605,"depth":78,"text":3606},{"id":3612,"depth":78,"text":3613},"AI Engineering","2026-07-20","2026-07-23","Step-by-step connector setup for every client: paste one URL into Claude web, one-click install in Cursor, developer mode in ChatGPT, one command in Claude Code. Sign in with your MiroMiro account, no key pasting.",[3651,3653,3656,3659],{"question":3592,"answer":3652},"Click the + button in the chat input, choose Connectors, then Add connector and Add custom connector. Name it, paste the server URL (for MiroMiro: https://miromiro.app/mcp), and click Add. Claude then opens a MiroMiro sign-in page; approve it and the tools are live. Leave the OAuth Client ID and Secret fields empty - discovery fills them in.",{"question":3654,"answer":3655},"Do I need an API key to use the MiroMiro MCP?","No. Paste the bare URL https://miromiro.app/mcp and the client prompts you to sign in with a MiroMiro account (free, 300 credits a month, no card). Pasting a key still works: send it as an Authorization: Bearer header, or append ?access_key=mm_live_your_key in clients without header support.",{"question":3657,"answer":3658},"How do I connect an MCP server to ChatGPT?","Settings, then Security and login, then enable Developer mode. Open Settings, then Plugins (chatgpt.com/plugins), create a New Plugin with the MCP server URL and Authentication set to OAuth, and sign in when prompted. ChatGPT requires remote (URL-based) servers - it cannot run local stdio packages.",{"question":3660,"answer":3661},"What is the difference between the remote MCP server and the npm package?","Same nine tools. The remote server (https://miromiro.app/mcp) is a hosted URL - nothing to install, works in browser clients like claude.ai and ChatGPT. The npm package (npx miromiro-mcp) runs locally and reads the key from an env var, which keeps it out of any URL.",{},"/api-blog/connect-miromiro-mcp-claude-cursor-chatgpt",[3665,3666,2423,2421],"figma-mcp-setup-and-alternatives","add-design-extraction-to-claude-code-mcp",{"title":2834,"description":3649},"Connect the MiroMiro MCP to Claude, Cursor, ChatGPT & Codex","api-blog/connect-miromiro-mcp-claude-cursor-chatgpt",[3671,3220,2934,3054,3672,3673,3674],"mcp","codex","connectors","ai agents","q5YZP-NEp3SpYRPWTI4lt7wrnktpj82H4ERiji1SN0Y",{"id":3677,"title":3678,"author":6,"body":3679,"category":2400,"date":2807,"dateModified":2417,"description":4338,"extension":2403,"faqs":4339,"image":2417,"meta":4349,"navigation":533,"path":4350,"readingTime":164,"relatedSlugs":4351,"seo":4354,"seoTitle":2417,"stem":4355,"tags":4356,"__hash__":4359},"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":3680,"toc":4322},[3681,3688,3691,3695,3698,3724,3727,3893,3896,3902,3917,3921,3924,3962,3965,3969,3972,4008,4010,4013,4074,4078,4161,4167,4169,4172,4202,4231,4240,4242,4252,4254,4258,4261,4265,4278,4282,4285,4289,4292,4296,4319],[11,3682,3683,3684,3687],{},"The fastest way to extract design tokens from a website in Python is Playwright plus ",[15,3685,3686],{},"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,3689,3690],{},"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.",[23,3692,3694],{"id":3693},"extract-colors-with-playwright","Extract colors with Playwright",[11,3696,3697],{},"First, install Playwright and a browser:",[31,3699,3701],{"className":1835,"code":3700,"language":1837,"meta":36,"style":36},"pip install playwright\nplaywright install chromium\n",[15,3702,3703,3714],{"__ignoreMap":36},[40,3704,3705,3708,3711],{"class":42,"line":43},[40,3706,3707],{"class":147},"pip",[40,3709,3710],{"class":69}," install",[40,3712,3713],{"class":69}," playwright\n",[40,3715,3716,3719,3721],{"class":42,"line":50},[40,3717,3718],{"class":147},"playwright",[40,3720,3710],{"class":69},[40,3722,3723],{"class":69}," chromium\n",[11,3725,3726],{},"Then collect styles from every element and count them:",[31,3728,3732],{"className":3729,"code":3730,"language":3731,"meta":36,"style":36},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","python",[15,3733,3734,3739,3744,3748,3753,3758,3763,3768,3773,3778,3783,3788,3793,3798,3803,3807,3812,3816,3821,3825,3830,3835,3839,3844,3849,3854,3859,3864,3869,3873,3878,3883,3888],{"__ignoreMap":36},[40,3735,3736],{"class":42,"line":43},[40,3737,3738],{},"from collections import Counter\n",[40,3740,3741],{"class":42,"line":50},[40,3742,3743],{},"from playwright.sync_api import sync_playwright\n",[40,3745,3746],{"class":42,"line":78},[40,3747,534],{"emptyLinePlaceholder":533},[40,3749,3750],{"class":42,"line":99},[40,3751,3752],{},"COLLECT = \"\"\"\n",[40,3754,3755],{"class":42,"line":120},[40,3756,3757],{},"() => {\n",[40,3759,3760],{"class":42,"line":135},[40,3761,3762],{},"  const els = [...document.querySelectorAll('body *')].slice(0, 5000);\n",[40,3764,3765],{"class":42,"line":141},[40,3766,3767],{},"  const out = { colors: [], fonts: [], sizes: [], radii: [], shadows: [] };\n",[40,3769,3770],{"class":42,"line":164},[40,3771,3772],{},"  for (const el of els) {\n",[40,3774,3775],{"class":42,"line":185},[40,3776,3777],{},"    const s = getComputedStyle(el);\n",[40,3779,3780],{"class":42,"line":220},[40,3781,3782],{},"    out.colors.push(s.color, s.backgroundColor, s.borderTopColor);\n",[40,3784,3785],{"class":42,"line":234},[40,3786,3787],{},"    out.fonts.push(s.fontFamily);\n",[40,3789,3790],{"class":42,"line":240},[40,3791,3792],{},"    out.sizes.push(s.fontSize);\n",[40,3794,3795],{"class":42,"line":261},[40,3796,3797],{},"    out.radii.push(s.borderTopLeftRadius);\n",[40,3799,3800],{"class":42,"line":281},[40,3801,3802],{},"    out.shadows.push(s.boxShadow);\n",[40,3804,3805],{"class":42,"line":302},[40,3806,1340],{},[40,3808,3809],{"class":42,"line":345},[40,3810,3811],{},"  return out;\n",[40,3813,3814],{"class":42,"line":359},[40,3815,415],{},[40,3817,3818],{"class":42,"line":365},[40,3819,3820],{},"\"\"\"\n",[40,3822,3823],{"class":42,"line":371},[40,3824,534],{"emptyLinePlaceholder":533},[40,3826,3827],{"class":42,"line":386},[40,3828,3829],{},"def top(values, n=8, skip=(\"\", \"none\", \"normal\", \"auto\", \"0px\", \"rgba(0, 0, 0, 0)\")):\n",[40,3831,3832],{"class":42,"line":392},[40,3833,3834],{},"    return Counter(v for v in values if v not in skip).most_common(n)\n",[40,3836,3837],{"class":42,"line":398},[40,3838,534],{"emptyLinePlaceholder":533},[40,3840,3841],{"class":42,"line":412},[40,3842,3843],{},"with sync_playwright() as p:\n",[40,3845,3846],{"class":42,"line":1011},[40,3847,3848],{},"    browser = p.chromium.launch()\n",[40,3850,3851],{"class":42,"line":1046},[40,3852,3853],{},"    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n",[40,3855,3856],{"class":42,"line":1054},[40,3857,3858],{},"    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n",[40,3860,3861],{"class":42,"line":1059},[40,3862,3863],{},"    raw = page.evaluate(COLLECT)\n",[40,3865,3866],{"class":42,"line":1094},[40,3867,3868],{},"    browser.close()\n",[40,3870,3871],{"class":42,"line":1152},[40,3872,534],{"emptyLinePlaceholder":533},[40,3874,3875],{"class":42,"line":1167},[40,3876,3877],{},"for name in (\"colors\", \"fonts\", \"sizes\", \"radii\", \"shadows\"):\n",[40,3879,3880],{"class":42,"line":1173},[40,3881,3882],{},"    print(f\"\\n== {name} ==\")\n",[40,3884,3885],{"class":42,"line":1221},[40,3886,3887],{},"    for value, count in top(raw[name]):\n",[40,3889,3890],{"class":42,"line":1226},[40,3891,3892],{},"        print(f\"{count:>5}  {value}\")\n",[11,3894,3895],{},"Run it and you get something like:",[31,3897,3900],{"className":3898,"code":3899,"language":3149},[3147],"== 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",[15,3901,3899],{"__ignoreMap":36},[11,3903,3904,3905,3908,3909,3912,3913,3916],{},"That third line is the brand color, found by nothing smarter than counting. ",[15,3906,3907],{},"getComputedStyle"," has already done the hard work for you: it resolved every CSS variable chain, every ",[15,3910,3911],{},"var(--a, var(--b))"," fallback, every cascade conflict, and turned Tailwind utility soup into plain ",[15,3914,3915],{},"rgb()"," values.",[23,3918,3920],{"id":3919},"fonts-and-the-type-scale","Fonts and the type scale",[11,3922,3923],{},"The same collection already has them. Font families come back as full stacks, so take the first entry:",[31,3925,3927],{"className":3729,"code":3926,"language":3731,"meta":36,"style":36},"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",[15,3928,3929,3934,3939,3943,3948,3953,3957],{"__ignoreMap":36},[40,3930,3931],{"class":42,"line":43},[40,3932,3933],{},"families = Counter(f.split(\",\")[0].strip('\"\\' ') for f in raw[\"fonts\"])\n",[40,3935,3936],{"class":42,"line":50},[40,3937,3938],{},"print(families.most_common(5))\n",[40,3940,3941],{"class":42,"line":78},[40,3942,534],{"emptyLinePlaceholder":533},[40,3944,3945],{"class":42,"line":99},[40,3946,3947],{},"sizes = sorted(\n",[40,3949,3950],{"class":42,"line":120},[40,3951,3952],{},"    {float(s.replace(\"px\", \"\")) for s, c in top(raw[\"sizes\"], n=20)},\n",[40,3954,3955],{"class":42,"line":135},[40,3956,596],{},[40,3958,3959],{"class":42,"line":141},[40,3960,3961],{},"print(sizes)  # e.g. [12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 36.0, 56.0]\n",[11,3963,3964],{},"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.",[23,3966,3968],{"id":3967},"from-raw-values-to-actual-tokens","From raw values to actual tokens",[11,3970,3971],{},"Raw counts are not tokens yet. Three cleanup steps matter:",[1878,3973,3974,3983,3995],{},[423,3975,3976,1921,3979,3982],{},[1884,3977,3978],{},"Drop the defaults.",[15,3980,3981],{},"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.",[423,3984,3985,1921,3988,448,3991,3994],{},[1884,3986,3987],{},"Merge near-duplicates.",[15,3989,3990],{},"rgb(99, 91, 255)",[15,3992,3993],{},"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.",[423,3996,3997,4000,4001,4004,4005,4007],{},[1884,3998,3999],{},"Name by role, not by value."," The most frequent saturated color on buttons and links is ",[15,4002,4003],{},"primary",". The dominant text color is ",[15,4006,3149],{},". Frequency plus element context gets you there; k-means clustering is overkill for this and I would skip it.",[23,4009,3493],{"id":3492},[11,4011,4012],{},"Real gotchas, in the order they will bite you:",[420,4014,4015,4025,4031,4048,4061],{},[423,4016,4017,4020,4021,4024],{},[1884,4018,4019],{},"Your viewport is a filter."," Computed styles reflect the current viewport only. A ",[15,4022,4023],{},"@media (max-width: 640px)"," override is invisible at 1440px. Extract at two viewports (mobile and desktop) if you care about responsive tokens.",[423,4026,4027,4030],{},[1884,4028,4029],{},"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.",[423,4032,4033,1921,4036,4039,4040,4043,4044,4047],{},[1884,4034,4035],{},"Fonts can lie.",[15,4037,4038],{},"font-family"," lists what CSS asked for, not what loaded. With ",[15,4041,4042],{},"font-display: swap"," the brand font may never arrive in a headless run. Await ",[15,4045,4046],{},"document.fonts.ready"," before collecting if exact loaded fonts matter.",[423,4049,4050,4056,4057,4060],{},[1884,4051,4052,4053,578],{},"Gradients are not in ",[15,4054,4055],{},"backgroundColor"," They live in ",[15,4058,4059],{},"backgroundImage",". If a brand is gradient-heavy, collect that property too or you will report their hero as plain white.",[423,4062,4063,2342,4066,4069,4070,4073],{},[1884,4064,4065],{},"Shadow DOM and iframes are invisible",[15,4067,4068],{},"querySelectorAll",". Web-component-heavy sites need you to walk ",[15,4071,4072],{},"shadowRoot","s explicitly.",[23,4075,4077],{"id":4076},"diy-vs-parsing-css-vs-an-api","DIY vs parsing CSS vs an API",[2105,4079,4080,4102],{},[2108,4081,4082],{},[2111,4083,4084,4087,4090,4093,4096,4099],{},[2114,4085,4086],{},"Approach",[2114,4088,4089],{},"Tailwind / utility CSS",[2114,4091,4092],{},"Runtime CSS-in-JS",[2114,4094,4095],{},"Resolves var() and cascade",[2114,4097,4098],{},"Media queries",[2114,4100,4101],{},"Maintenance",[2127,4103,4104,4121,4140],{},[2111,4105,4106,4109,4111,4113,4115,4118],{},[2132,4107,4108],{},"Playwright + computed styles (this guide)",[2132,4110,2168],{},[2132,4112,2168],{},[2132,4114,2168],{},[2132,4116,4117],{},"Current viewport only",[2132,4119,4120],{},"Browser infra is yours to run",[2111,4122,4123,4126,4129,4132,4134,4137],{},[2132,4124,4125],{},"Parse CSS files yourself (tinycss2, no browser)",[2132,4127,4128],{},"Painful",[2132,4130,4131],{},"No",[2132,4133,4131],{},[2132,4135,4136],{},"Declared, but unused values included",[2132,4138,4139],{},"Low, but wrong on modern sites",[2111,4141,4142,4145,4147,4153,4155,4158],{},[2132,4143,4144],{},"MiroMiro extract API",[2132,4146,2168],{},[2132,4148,4149,4150,4152],{},"Yes, with ",[15,4151,2631],{}," (+5 credits, runs the page in a real browser)",[2132,4154,2168],{},[2132,4156,4157],{},"Yes, all declared breakpoints",[2132,4159,4160],{},"None",[11,4162,4163,4164,4166],{},"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 ",[15,4165,2615],{}," fallbacks yourself is a project, not a script.",[23,4168,2191],{"id":2190},[11,4170,4171],{},"The dedupe, the ranking, the var() chains, the resets, and every declared breakpoint - that is what our extract endpoint does for a living:",[31,4173,4175],{"className":1835,"code":4174,"language":1837,"meta":36,"style":36},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[15,4176,4177,4190],{"__ignoreMap":36},[40,4178,4179,4181,4183,4186,4188],{"class":42,"line":43},[40,4180,2207],{"class":147},[40,4182,66],{"class":46},[40,4184,4185],{"class":69},"https://miromiro.app/api/v1/extract?url=stripe.com",[40,4187,60],{"class":46},[40,4189,2220],{"class":329},[40,4191,4192,4194,4196,4198,4200],{"class":42,"line":50},[40,4193,2225],{"class":69},[40,4195,66],{"class":46},[40,4197,2230],{"class":69},[40,4199,2233],{"class":329},[40,4201,2609],{"class":46},[11,4203,4204,4205,1302,4208,1302,4211,1302,4214,1302,4217,1302,4220,1302,4223,4226,4227,4230],{},"The JSON response has ",[15,4206,4207],{},"colors",[15,4209,4210],{},"fontFamilies",[15,4212,4213],{},"fontSizes",[15,4215,4216],{},"fontWeights",[15,4218,4219],{},"spacing",[15,4221,4222],{},"borderRadiuses",[15,4224,4225],{},"boxShadows",", and more, already deduped and ranked. Trim it with ",[15,4228,4229],{},"?fields=colors,spacing"," if you only need part of it.",[11,4232,4233,4234,4237,4238,578],{},"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 ",[1955,4235,4236],{"href":2283},"get a free API key"," with 300 credits a month, no card, or try it without signing up in the ",[1955,4239,2758],{"href":2757},[23,4241,2289],{"id":2288},[11,4243,4244,4245,4247,4248,4251],{},"Playwright plus ",[15,4246,3907],{}," plus a ",[15,4249,4250],{},"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.",[23,4253,2296],{"id":2295},[2298,4255,4257],{"id":4256},"can-i-extract-design-tokens-without-a-headless-browser","Can I extract design tokens without a headless browser?",[11,4259,4260],{},"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.",[2298,4262,4264],{"id":4263},"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,4266,4267,4268,4270,4271,2342,4274,4277],{},"Ignore the class names entirely and read computed styles. ",[15,4269,3907],{}," resolves ",[15,4272,4273],{},"bg-indigo-600",[15,4275,4276],{},"rgb(79, 70, 229)"," like any other CSS, so nothing in this guide changes.",[2298,4279,4281],{"id":4280},"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,4283,4284],{},"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.",[2298,4286,4288],{"id":4287},"how-many-pages-should-i-sample","How many pages should I sample?",[11,4290,4291],{},"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.",[23,4293,4295],{"id":4294},"related-reading","Related reading",[420,4297,4298,4305,4312],{},[423,4299,4300,4304],{},[1955,4301,4303],{"href":4302},"/api/design-tokens-api","Design tokens API"," - the endpoint this post's shortcut uses",[423,4306,4307,4311],{},[1955,4308,4310],{"href":4309},"/api/docs/design-tokens","Design tokens docs"," - full response shape and field list",[423,4313,4314,4318],{},[1955,4315,4317],{"href":4316},"/api/use-cases/ai-agent-ground-truth","Design system for AI coding agents"," - why agents need real tokens instead of guessing from screenshots",[2380,4320,4321],{},"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":36,"searchDepth":50,"depth":50,"links":4323},[4324,4325,4326,4327,4328,4329,4330,4331,4337],{"id":3693,"depth":50,"text":3694},{"id":3919,"depth":50,"text":3920},{"id":3967,"depth":50,"text":3968},{"id":3492,"depth":50,"text":3493},{"id":4076,"depth":50,"text":4077},{"id":2190,"depth":50,"text":2191},{"id":2288,"depth":50,"text":2289},{"id":2295,"depth":50,"text":2296,"children":4332},[4333,4334,4335,4336],{"id":4256,"depth":78,"text":4257},{"id":4263,"depth":78,"text":4264},{"id":4280,"depth":78,"text":4281},{"id":4287,"depth":78,"text":4288},{"id":4294,"depth":50,"text":4295},"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.",[4340,4342,4344,4346],{"question":4257,"answer":4341},"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":4264,"answer":4343},"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":4281,"answer":4345},"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":4347,"answer":4348},"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",[2823,4352,4353],"design-token-types-reference","how-to-extract-website-color-palette-programmatically",{"title":3678,"description":4338},"api-blog/how-to-extract-design-tokens-from-any-website-in-python",[4357,3731,3718,774,4358],"design tokens","web extraction","iqCbsKG2vfjjtycBbHVdj7aYO7WRGPzXyDx5GBqe7ik",1792517975022]