[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"i-local-icon:chevron-up":3,"\u002Fblog\u002Fnuxt-ui-component-detection-css-tree-shaking":8,"i-local-icon:calendar-days":547,"i-local-icon:tag":549},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    stroke-width=\"1.5\"\n    d=\"m4.5 15.75l7.5-7.5l7.5 7.5\" \u002F>",{"id":9,"title":10,"body":11,"categories_slug":533,"comment_count":537,"date_published":538,"description":539,"extension":540,"meta":541,"navigation":542,"path":543,"seo":544,"stem":545,"__hash__":546},"content\u002Fblog\u002F25.nuxt-ui-component-detection-css-tree-shaking.md","Reduce Nuxt UI CSS by half with experimental component detection",{"type":12,"value":13,"toc":521},"minimark",[14,18,23,28,37,44,51,55,62,147,150,192,210,221,235,239,242,245,278,281,287,343,347,352,359,367,391,406,410,431,434,486,489,493,496,517],[15,16,10],"h1",{"id":17},"reduce-nuxt-ui-css-by-half-with-experimental-component-detection",[19,20],"meta-card",{":categories":21,":date":22},"categories_slug","date_published",[24,25,27],"h2",{"id":26},"overview","Overview",[29,30,31,32,36],"p",{},"Nuxt UI already tree shakes component ",[33,34,35],"em",{},"code"," so that components you don't use never end up in the JavaScript bundle. However, this was not true for the CSS. Every component in the library ships a theme file full of Tailwind classes, and those classes all got compiled into the stylesheet whether you used the component or not.",[29,38,39,40,43],{},"Nuxt UI 4 added an experimental option that fixes this. It scans your source for ",[35,41,42],{},"U*"," components, works out their internal dependencies, and only feeds those theme files to Tailwind.",[29,45,46,47,50],{},"On this Bordermedia site, the main stylesheet went from 247 kB down to 118 kB. Gzipped, that is 33 kB down to 18 kB, and ",[35,48,49],{},"entry.css"," is render blocking, so greatly affects perceived load time and things like your Google Lighthouse scores.",[24,52,54],{"id":53},"enabling-it","Enabling it",[29,56,57,58,61],{},"Super simple, one option in ",[35,59,60],{},"nuxt.config.ts",":",[63,64,69],"pre",{"className":65,"code":66,"language":67,"meta":68,"style":68},"language-javascript shiki shiki-themes github-dark","export default defineNuxtConfig({\n  modules: [\"@nuxt\u002Fui\"],\n  ui: {\n    experimental: {\n      componentDetection: true,\n    },\n  },\n});\n","javascript","",[35,70,71,91,104,110,116,129,135,141],{"__ignoreMap":68},[72,73,76,80,83,87],"span",{"class":74,"line":75},"line",1,[72,77,79],{"class":78},"snl16","export",[72,81,82],{"class":78}," default",[72,84,86],{"class":85},"svObZ"," defineNuxtConfig",[72,88,90],{"class":89},"s95oV","({\n",[72,92,94,97,101],{"class":74,"line":93},2,[72,95,96],{"class":89},"  modules: [",[72,98,100],{"class":99},"sU2Wk","\"@nuxt\u002Fui\"",[72,102,103],{"class":89},"],\n",[72,105,107],{"class":74,"line":106},3,[72,108,109],{"class":89},"  ui: {\n",[72,111,113],{"class":74,"line":112},4,[72,114,115],{"class":89},"    experimental: {\n",[72,117,119,122,126],{"class":74,"line":118},5,[72,120,121],{"class":89},"      componentDetection: ",[72,123,125],{"class":124},"sDLfK","true",[72,127,128],{"class":89},",\n",[72,130,132],{"class":74,"line":131},6,[72,133,134],{"class":89},"    },\n",[72,136,138],{"class":74,"line":137},7,[72,139,140],{"class":89},"  },\n",[72,142,144],{"class":74,"line":143},8,[72,145,146],{"class":89},"});\n",[29,148,149],{},"Restart the dev server and you should see the detection result in the build log:",[63,151,155],{"className":152,"code":153,"language":154,"meta":68,"style":68},"language-bash shiki shiki-themes github-dark","✔ Nuxt UI detected 9 components in use (including dependencies)\n","bash",[35,156,157],{"__ignoreMap":68},[72,158,159,162,165,168,171,174,177,180,183,186,189],{"class":74,"line":75},[72,160,161],{"class":85},"✔",[72,163,164],{"class":99}," Nuxt",[72,166,167],{"class":99}," UI",[72,169,170],{"class":99}," detected",[72,172,173],{"class":124}," 9",[72,175,176],{"class":99}," components",[72,178,179],{"class":99}," in",[72,181,182],{"class":99}," use",[72,184,185],{"class":89}," (including ",[72,187,188],{"class":99},"dependencies",[72,190,191],{"class":89},")\n",[29,193,194,195,198,199,198,202,205,206,209],{},"Nine in my case. This site only uses ",[35,196,197],{},"UButton",", ",[35,200,201],{},"UFormField",[35,203,204],{},"UInput"," and ",[35,207,208],{},"UTextarea",". The other five are internal dependencies that Nuxt UI pulled in on its own.",[29,211,212,213,216,217,220],{},"As of Nuxt UI 4.11.0, the same option is now available for the vite plugin. So if you are running Nuxt UI in a plain Vue and Vite app, it goes in the ",[35,214,215],{},"ui()"," plugin options in ",[35,218,219],{},"vite.config.ts"," instead.",[222,223,224],"blockquote",{},[29,225,226,230,231,234],{},[227,228,229],"strong",{},"Note:"," Pass an array of component names if there are Nuxt UI components that cannot be detected. For example: ",[35,232,233],{},"componentDetection: ['Modal', 'DropdownMenu', 'Popover']",". See below for further details.",[24,236,238],{"id":237},"measuring-the-difference","Measuring the difference",[29,240,241],{},"The CSS size saving will depend entirely on how much of the library you use. A dashboard using half of Nuxt UI will save far less than a marketing site that is using only four components.",[29,243,244],{},"Build once with the option off:",[63,246,248],{"className":152,"code":247,"language":154,"meta":68,"style":68},"npm run build\nls -l .output\u002Fpublic\u002F_nuxt\u002Fentry.*.css\n",[35,249,250,261],{"__ignoreMap":68},[72,251,252,255,258],{"class":74,"line":75},[72,253,254],{"class":85},"npm",[72,256,257],{"class":99}," run",[72,259,260],{"class":99}," build\n",[72,262,263,266,269,272,275],{"class":74,"line":93},[72,264,265],{"class":85},"ls",[72,267,268],{"class":124}," -l",[72,270,271],{"class":99}," .output\u002Fpublic\u002F_nuxt\u002Fentry.",[72,273,274],{"class":124},"*",[72,276,277],{"class":99},".css\n",[29,279,280],{},"Then turn it on, build again, and compare.",[29,282,283,284,286],{},"Here is what changed on my Bordermedia site for ",[35,285,49],{},".",[288,289,290,306],"table",{},[291,292,293],"thead",{},[294,295,296,300,303],"tr",{},[297,298,299],"th",{},"Build",[297,301,302],{},"Raw",[297,304,305],{},"Gzip",[307,308,309,321,332],"tbody",{},[294,310,311,315,318],{},[312,313,314],"td",{},"Detection off",[312,316,317],{},"252,963 B",[312,319,320],{},"33,190 B",[294,322,323,326,329],{},[312,324,325],{},"Detection on",[312,327,328],{},"120,818 B",[312,330,331],{},"17,978 B",[294,333,334,337,340],{},[312,335,336],{},"Saved",[312,338,339],{},"132,145 B",[312,341,342],{},"15,212 B",[24,344,346],{"id":345},"gotchas","Gotchas",[348,349,351],"h3",{"id":350},"detection-runs-when-the-dev-server-starts","Detection runs when the dev server starts",[29,353,354,355,358],{},"New components are picked up on the next dev server start. So if you add a new component to your code mid session then its styles may not be there. It does log ",[35,356,357],{},"Nuxt UI detected new components:"," when it catches one during a rebuild, but if something looks unstyled, trying a restart should fix the problem.",[348,360,362,363,366],{"id":361},"only-the-app-folder-gets-scanned-not-markdown","Only the ",[35,364,365],{},"app"," folder gets scanned, not Markdown",[29,368,369,370,373,374,198,377,198,380,205,383,386,387,390],{},"In a Nuxt project the scan covers the ",[35,371,372],{},"app\u002F"," directories and looks at ",[35,375,376],{},".vue",[35,378,379],{},".ts",[35,381,382],{},".js",[35,384,385],{},".tsx"," files. Markdown is not scanned. If you use Nuxt UI components inside ",[35,388,389],{},"content\u002F*.md"," through MDC, they will not be detected.",[29,392,393,394,397,398,401,402,405],{},"Prose components are safe. Nuxt UI adds the whole prose theme automatically when it sees ",[35,395,396],{},"@nuxt\u002Fcontent"," or ",[35,399,400],{},"@nuxtjs\u002Fmdc"," in your project, so ",[35,403,404],{},"\u003CContentRenderer>"," output keeps its styling.",[348,407,409],{"id":408},"dynamic-components-need-to-be-listed","Dynamic components need to be listed",[29,411,412,413,205,415,418,419,422,423,426,427,430],{},"The scan is a regex looking for ",[35,414,197],{},[35,416,417],{},"\u003Cu-button>"," style names in your source. It cannot see a component whose name is built at runtime, so ",[35,420,421],{},"\u003Ccomponent :is=\"...\">",", anything opened through ",[35,424,425],{},"useOverlay()",", and toasts from ",[35,428,429],{},"useToast()"," will not be found.",[29,432,433],{},"Pass an array of components to enforce that the CSS is included:",[63,435,437],{"className":65,"code":436,"language":67,"meta":68,"style":68},"ui: {\n  experimental: {\n    componentDetection: [\"Modal\", \"DropdownMenu\", \"Toast\"],\n  },\n},\n",[35,438,439,447,454,477,481],{"__ignoreMap":68},[72,440,441,444],{"class":74,"line":75},[72,442,443],{"class":85},"ui",[72,445,446],{"class":89},": {\n",[72,448,449,452],{"class":74,"line":93},[72,450,451],{"class":85},"  experimental",[72,453,446],{"class":89},[72,455,456,459,462,465,467,470,472,475],{"class":74,"line":106},[72,457,458],{"class":85},"    componentDetection",[72,460,461],{"class":89},": [",[72,463,464],{"class":99},"\"Modal\"",[72,466,198],{"class":89},[72,468,469],{"class":99},"\"DropdownMenu\"",[72,471,198],{"class":89},[72,473,474],{"class":99},"\"Toast\"",[72,476,103],{"class":89},[72,478,479],{"class":74,"line":112},[72,480,140],{"class":89},[72,482,483],{"class":74,"line":118},[72,484,485],{"class":89},"},\n",[29,487,488],{},"The array adds to automatic detection rather than replacing it, and Nuxt UI warns you at build time if you list a name it does not recognise.",[24,490,492],{"id":491},"documentation","Documentation",[29,494,495],{},"The option is documented on both installation pages:",[497,498,499,510],"ul",{},[500,501,502],"li",{},[503,504,509],"a",{":target":505,"href":506,"rel":507},"\\_blank","https:\u002F\u002Fui.nuxt.com\u002Fdocs\u002Fgetting-started\u002Finstallation\u002Fnuxt#experimentalcomponentdetection",[508],"nofollow","Nuxt UI docs - Nuxt installation",[500,511,512],{},[503,513,516],{":target":505,"href":514,"rel":515},"https:\u002F\u002Fui.nuxt.com\u002Fdocs\u002Fgetting-started\u002Finstallation\u002Fvue#experimentalcomponentdetection",[508],"Nuxt UI docs - Vue installation",[518,519,520],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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);}",{"title":68,"searchDepth":93,"depth":93,"links":522},[523,524,525,526,532],{"id":26,"depth":93,"text":27},{"id":53,"depth":93,"text":54},{"id":237,"depth":93,"text":238},{"id":345,"depth":93,"text":346,"children":527},[528,529,531],{"id":350,"depth":106,"text":351},{"id":361,"depth":106,"text":530},"Only the app folder gets scanned, not Markdown",{"id":408,"depth":106,"text":409},{"id":491,"depth":93,"text":492},[534,535,536],"nuxt","vue","css",null,"2026-08-23","Nuxt UI 4 has an experimental componentDetection option that only generates theme CSS for the Nuxt UI components you actually use. On this site it cut the main stylesheet in half. Here is how to enable it, how to measure the difference, and what to watch out for.","md",{},true,"\u002Fblog\u002Fnuxt-ui-component-detection-css-tree-shaking",{"title":10,"description":539},"blog\u002F25.nuxt-ui-component-detection-css-tree-shaking","BpC894TG3YEkncTzJet5bxwsZ-YSwXLi1C1fovW2wo0",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":548},"\u003Cpath\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    stroke-width=\"1.5\"\n    d=\"M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12zM12 15h.008v.008H12zm0 2.25h.008v.008H12zM9.75 15h.008v.008H9.75zm0 2.25h.008v.008H9.75zM7.5 15h.008v.008H7.5zm0 2.25h.008v.008H7.5zm6.75-4.5h.008v.008h-.008zm0 2.25h.008v.008h-.008zm0 2.25h.008v.008h-.008zm2.25-4.5h.008v.008H16.5zm0 2.25h.008v.008H16.5z\" \u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":550},"\u003Cg\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    stroke-width=\"1.5\">\n    \u003Cpath\n      d=\"M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.1 18.1 0 0 0 5.224-5.223c.54-.827.368-1.908-.33-2.607l-9.583-9.58A2.25 2.25 0 0 0 9.568 3\" \u002F>\n    \u003Cpath d=\"M6 6h.008v.008H6z\" \u002F>\n  \u003C\u002Fg>"]