[{"data":1,"prerenderedAt":2450},["ShallowReactive",2],{"blog-oxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba-es":3},{"post":4,"surround":1337,"translatedPost":1348},{"id":5,"title":6,"author":7,"body":8,"date":1321,"dateModified":1322,"description":1323,"extension":1324,"img":1325,"meta":1326,"navigation":413,"path":1327,"published":413,"readingTime":170,"seo":1328,"sitemap":1329,"slug":1325,"stem":1330,"tags":1331,"union":1335,"__hash__":1336},"blog_es\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba.md","oxlint-tailwindcss: el plugin de linting que Tailwind v4 necesitaba","Sergio Azócar",{"type":9,"value":10,"toc":1303},"minimark",[11,16,20,32,35,39,51,54,59,80,220,235,238,248,252,256,259,267,315,326,334,368,380,439,453,461,490,498,555,573,587,591,599,621,648,663,682,709,713,721,729,733,745,765,773,777,784,881,884,888,907,964,971,975,986,1021,1024,1028,1049,1055,1260,1263,1267,1282,1285,1300],[12,13,15],"h2",{"id":14},"del-problema-al-open-source-la-oportunidad-perfecta-para-contribuir","Del problema al open-source: la oportunidad perfecta para contribuir",[17,18,19],"p",{},"El día que el equipo de oxc abrió el alpha de plugins nativos para oxlint, fue la excusa perfecta para solucionar un problema que llevaba tiempo teniendo en mi trabajo actual, lintear las clases de Tailwind CSS con oxlint.",[17,21,22,23,27,28,31],{},"Si usas Tailwind CSS v4 con oxlint, las opciones de linting existentes no están pensadas para ese combo. ",[24,25,26],"code",{},"eslint-plugin-tailwindcss"," es sólido pero vive en el mundo de ESLint y su soporte de v4 todavía es parcial. ",[24,29,30],{},"eslint-plugin-better-tailwindcss"," funciona en oxlint a través de la capa de compatibilidad jsPlugins, y hace el trabajo — pero no es un plugin nativo y sus reglas son más acotadas. Ninguno fue diseñado específicamente para oxlint + Tailwind CSS v4.",[17,33,34],{},"Así que lo construí.",[12,36,38],{"id":37},"qué-es-oxlint-tailwindcss","Qué es oxlint-tailwindcss",[17,40,41,42,46,47,50],{},"Un plugin ",[43,44,45],"strong",{},"nativo"," de oxlint con 22 reglas de linting diseñadas exclusivamente para Tailwind CSS v4. No es un port de ESLint ni un wrapper, usa directamente la API de ",[24,48,49],{},"@oxlint\u002Fplugins",". Solo 2 dependencias en runtime.",[17,52,53],{},"Esto importa porque al ser nativo, comparte el mismo ciclo de parseo que oxlint. No hay overhead de interoperabilidad, no hay capa de traducción. Es tan rápido como oxlint mismo y se nota.",[55,56,58],"h3",{"id":57},"funciona-sin-configuración","Funciona sin configuración",[17,60,61,62,65,66,65,69,65,72,75,76,79],{},"El plugin auto-detecta tu entry point de Tailwind CSS. Si tu archivo se llama ",[24,63,64],{},"app.css",", ",[24,67,68],{},"globals.css",[24,70,71],{},"main.css",[24,73,74],{},"tailwind.css"," (o cualquiera de los nombres convencionales) y contiene ",[24,77,78],{},"@import \"tailwindcss\"",", lo encuentra solo.",[81,82,87],"pre",{"className":83,"code":84,"language":85,"meta":86,"style":86},"language-json shiki shiki-themes material-theme-ocean","{\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\"\n  }\n}\n","json","",[24,88,89,98,128,143,168,188,208,214],{"__ignoreMap":86},[90,91,94],"span",{"class":92,"line":93},"line",1,[90,95,97],{"class":96},"sAklC","{\n",[90,99,101,104,108,111,114,117,119,123,125],{"class":92,"line":100},2,[90,102,103],{"class":96},"  \"",[90,105,107],{"class":106},"sJ14y","jsPlugins",[90,109,110],{"class":96},"\"",[90,112,113],{"class":96},":",[90,115,116],{"class":96}," [",[90,118,110],{"class":96},[90,120,122],{"class":121},"sfyAc","oxlint-tailwindcss",[90,124,110],{"class":96},[90,126,127],{"class":96},"],\n",[90,129,131,133,136,138,140],{"class":92,"line":130},3,[90,132,103],{"class":96},[90,134,135],{"class":106},"rules",[90,137,110],{"class":96},[90,139,113],{"class":96},[90,141,142],{"class":96}," {\n",[90,144,146,149,153,155,157,160,163,165],{"class":92,"line":145},4,[90,147,148],{"class":96},"    \"",[90,150,152],{"class":151},"s5Dmg","tailwindcss\u002Fno-unknown-classes",[90,154,110],{"class":96},[90,156,113],{"class":96},[90,158,159],{"class":96}," \"",[90,161,162],{"class":121},"error",[90,164,110],{"class":96},[90,166,167],{"class":96},",\n",[90,169,171,173,176,178,180,182,184,186],{"class":92,"line":170},5,[90,172,148],{"class":96},[90,174,175],{"class":151},"tailwindcss\u002Fno-conflicting-classes",[90,177,110],{"class":96},[90,179,113],{"class":96},[90,181,159],{"class":96},[90,183,162],{"class":121},[90,185,110],{"class":96},[90,187,167],{"class":96},[90,189,191,193,196,198,200,202,205],{"class":92,"line":190},6,[90,192,148],{"class":96},[90,194,195],{"class":151},"tailwindcss\u002Fenforce-sort-order",[90,197,110],{"class":96},[90,199,113],{"class":96},[90,201,159],{"class":96},[90,203,204],{"class":121},"warn",[90,206,207],{"class":96},"\"\n",[90,209,211],{"class":92,"line":210},7,[90,212,213],{"class":96},"  }\n",[90,215,217],{"class":92,"line":216},8,[90,218,219],{"class":96},"}\n",[17,221,222,223,226,227,230,231,234],{},"La auto-detección sigue ",[24,224,225],{},"@import"," statements un nivel de profundidad — incluyendo imports de paquetes como ",[24,228,229],{},"@import '@company\u002Ftheme\u002Ftailwind.config.css'",". En monorepos, la búsqueda se detiene en boundaries de ",[24,232,233],{},"package.json"," para que cada paquete resuelva su propio design system automáticamente.",[17,236,237],{},"El design system se carga una vez por entry point, con caché en memoria y en disco. En un monorepo con múltiples paquetes que comparten el mismo entry point, el design system se carga solo una vez.",[239,240,241],"blockquote",{},[17,242,243,244,247],{},"Si tienes un entry point distinto a lo convencional, puedes configurarlo en ",[24,245,246],{},"settings.tailwindcss.entryPoint"," o por regla.",[12,249,251],{"id":250},"_22-reglas-en-cuatro-categorías","22 reglas en cuatro categorías",[55,253,255],{"id":254},"correctness-evitar-errores-reales","Correctness — Evitar errores reales",[17,257,258],{},"Las reglas de correctness atrapan bugs antes de que lleguen al navegador.",[17,260,261,266],{},[43,262,263],{},[24,264,265],{},"no-unknown-classes"," detecta clases que no existen en tu design system y sugiere correcciones para typos:",[81,268,272],{"className":269,"code":270,"language":271,"meta":86,"style":86},"language-jsx shiki shiki-themes material-theme-ocean","\u003Cdiv className=\"flex itms-center bg-blu-500\" \u002F>\n\u002F\u002F                   ^^^^^^^^^^^\n\u002F\u002F \"itms-center\" is not a valid Tailwind CSS class.\n\u002F\u002F Did you mean \"items-center\"?\n","jsx",[24,273,274,299,305,310],{"__ignoreMap":86},[90,275,276,279,283,286,289,291,294,296],{"class":92,"line":93},[90,277,278],{"class":96},"\u003C",[90,280,282],{"class":281},"s-wAU","div",[90,284,285],{"class":106}," className",[90,287,288],{"class":96},"=",[90,290,110],{"class":96},[90,292,293],{"class":121},"flex itms-center bg-blu-500",[90,295,110],{"class":96},[90,297,298],{"class":96}," \u002F>\n",[90,300,301],{"class":92,"line":100},[90,302,304],{"class":303},"sC9rS","\u002F\u002F                   ^^^^^^^^^^^\n",[90,306,307],{"class":92,"line":130},[90,308,309],{"class":303},"\u002F\u002F \"itms-center\" is not a valid Tailwind CSS class.\n",[90,311,312],{"class":92,"line":145},[90,313,314],{"class":303},"\u002F\u002F Did you mean \"items-center\"?\n",[17,316,317,318,321,322,325],{},"Soporta ",[24,319,320],{},"allowlist"," para permitir clases custom que no están en tu design system e ",[24,323,324],{},"ignorePrefixes"," para saltarse prefijos que no son clases de Tailwind.",[17,327,328,333],{},[43,329,330],{},[24,331,332],{},"no-conflicting-classes"," te dice exactamente qué propiedad CSS está en conflicto y cuál clase gana:",[81,335,337],{"className":269,"code":336,"language":271,"meta":86,"style":86},"\u003Cdiv className=\"text-red-500 text-blue-500\" \u002F>\n\u002F\u002F \"text-red-500\" and \"text-blue-500\" affect \"color\".\n\u002F\u002F \"text-blue-500\" takes precedence (appears later).\n",[24,338,339,358,363],{"__ignoreMap":86},[90,340,341,343,345,347,349,351,354,356],{"class":92,"line":93},[90,342,278],{"class":96},[90,344,282],{"class":281},[90,346,285],{"class":106},[90,348,288],{"class":96},[90,350,110],{"class":96},[90,352,353],{"class":121},"text-red-500 text-blue-500",[90,355,110],{"class":96},[90,357,298],{"class":96},[90,359,360],{"class":92,"line":100},[90,361,362],{"class":303},"\u002F\u002F \"text-red-500\" and \"text-blue-500\" affect \"color\".\n",[90,364,365],{"class":92,"line":130},[90,366,367],{"class":303},"\u002F\u002F \"text-blue-500\" takes precedence (appears later).\n",[17,369,370,375,376,379],{},[43,371,372],{},[24,373,374],{},"no-dark-without-light"," detecta cuando usas ",[24,377,378],{},"dark:"," sin una clase base, algo que suele causar estilos faltantes en light mode:",[81,381,383],{"className":269,"code":382,"language":271,"meta":86,"style":86},"\u002F\u002F ❌ — ¿qué fondo tiene en light mode?\n\u003Cdiv className=\"dark:bg-gray-900\" \u002F>\n\n\u002F\u002F ✅\n\u003Cdiv className=\"bg-white dark:bg-gray-900\" \u002F>\n",[24,384,385,390,409,415,420],{"__ignoreMap":86},[90,386,387],{"class":92,"line":93},[90,388,389],{"class":303},"\u002F\u002F ❌ — ¿qué fondo tiene en light mode?\n",[90,391,392,394,396,398,400,402,405,407],{"class":92,"line":100},[90,393,278],{"class":96},[90,395,282],{"class":281},[90,397,285],{"class":106},[90,399,288],{"class":96},[90,401,110],{"class":96},[90,403,404],{"class":121},"dark:bg-gray-900",[90,406,110],{"class":96},[90,408,298],{"class":96},[90,410,411],{"class":92,"line":130},[90,412,414],{"emptyLinePlaceholder":413},true,"\n",[90,416,417],{"class":92,"line":145},[90,418,419],{"class":303},"\u002F\u002F ✅\n",[90,421,422,424,426,428,430,432,435,437],{"class":92,"line":170},[90,423,278],{"class":96},[90,425,282],{"class":281},[90,427,285],{"class":106},[90,429,288],{"class":96},[90,431,110],{"class":96},[90,433,434],{"class":121},"bg-white dark:bg-gray-900",[90,436,110],{"class":96},[90,438,298],{"class":96},[17,440,441,445,446,448,449,452],{},[43,442,443],{},[24,444,374],{}," chequea ",[24,447,378],{}," por defecto, pero la opción ",[24,450,451],{},"variants"," permite aplicar el mismo patrón a cualquier variante — útil si tu proyecto usa variantes custom.",[17,454,455,460],{},[43,456,457],{},[24,458,459],{},"no-contradicting-variants"," atrapa variantes redundantes donde la clase base ya aplica incondicionalmente:",[81,462,464],{"className":269,"code":463,"language":271,"meta":86,"style":86},"\u002F\u002F ❌ — dark:flex es redundante, flex ya aplica siempre\n\u003Cdiv className=\"flex dark:flex\" \u002F>\n",[24,465,466,471],{"__ignoreMap":86},[90,467,468],{"class":92,"line":93},[90,469,470],{"class":303},"\u002F\u002F ❌ — dark:flex es redundante, flex ya aplica siempre\n",[90,472,473,475,477,479,481,483,486,488],{"class":92,"line":100},[90,474,278],{"class":96},[90,476,282],{"class":281},[90,478,285],{"class":106},[90,480,288],{"class":96},[90,482,110],{"class":96},[90,484,485],{"class":121},"flex dark:flex",[90,487,110],{"class":96},[90,489,298],{"class":96},[17,491,492,497],{},[43,493,494],{},[24,495,496],{},"no-deprecated-classes"," reemplaza automáticamente las clases deprecadas en v4:",[81,499,501],{"className":269,"code":500,"language":271,"meta":86,"style":86},"\u002F\u002F ❌ v3\n\u003Cdiv className=\"flex-grow overflow-ellipsis decoration-slice\" \u002F>\n\n\u002F\u002F ✅ v4 (autofix)\n\u003Cdiv className=\"grow text-ellipsis box-decoration-slice\" \u002F>\n",[24,502,503,508,527,531,536],{"__ignoreMap":86},[90,504,505],{"class":92,"line":93},[90,506,507],{"class":303},"\u002F\u002F ❌ v3\n",[90,509,510,512,514,516,518,520,523,525],{"class":92,"line":100},[90,511,278],{"class":96},[90,513,282],{"class":281},[90,515,285],{"class":106},[90,517,288],{"class":96},[90,519,110],{"class":96},[90,521,522],{"class":121},"flex-grow overflow-ellipsis decoration-slice",[90,524,110],{"class":96},[90,526,298],{"class":96},[90,528,529],{"class":92,"line":130},[90,530,414],{"emptyLinePlaceholder":413},[90,532,533],{"class":92,"line":145},[90,534,535],{"class":303},"\u002F\u002F ✅ v4 (autofix)\n",[90,537,538,540,542,544,546,548,551,553],{"class":92,"line":170},[90,539,278],{"class":96},[90,541,282],{"class":281},[90,543,285],{"class":106},[90,545,288],{"class":96},[90,547,110],{"class":96},[90,549,550],{"class":121},"grow text-ellipsis box-decoration-slice",[90,552,110],{"class":96},[90,554,298],{"class":96},[17,556,557,558,561,562,565,566,561,569,572],{},"También ",[24,559,560],{},"flex-shrink"," → ",[24,563,564],{},"shrink"," y ",[24,567,568],{},"decoration-clone",[24,570,571],{},"box-decoration-clone",".",[17,574,575,576,581,582,572],{},"Y las clásicas ",[43,577,578],{},[24,579,580],{},"no-duplicate-classes"," (con autofix) y ",[43,583,584],{},[24,585,586],{},"no-unnecessary-whitespace",[55,588,590],{"id":589},"style-consistencia-del-equipo","Style — Consistencia del equipo",[17,592,593,598],{},[43,594,595],{},[24,596,597],{},"enforce-sort-order"," ordena las clases según el orden oficial de Tailwind CSS (con autofix), compatible con oxfmt y prettier-plugin-tailwindcss. Su modo strict agrupa las clases por prefijo de variante, ordena dentro de cada grupo y ordena los grupos por prioridad de variante.",[17,600,601,606,607,610,611,65,614,610,617,620],{},[43,602,603],{},[24,604,605],{},"enforce-shorthand"," convierte ",[24,608,609],{},"mt-2 mr-2 mb-2 ml-2"," en ",[24,612,613],{},"m-2",[24,615,616],{},"w-full h-full",[24,618,619],{},"size-full",", y muchas más combinaciones. Todo con autofix.",[17,622,623,628,629,561,632,65,635,561,638,641,642,647],{},[43,624,625],{},[24,626,627],{},"enforce-logical"," convierte propiedades físicas en lógicas para soporte LTR\u002FRTL: ",[24,630,631],{},"ml-4",[24,633,634],{},"ms-4",[24,636,637],{},"left-0",[24,639,640],{},"start-0",". Su inversa, ",[43,643,644],{},[24,645,646],{},"enforce-physical",", hace lo contrario para proyectos que son solo LTR y prefieren consistencia con propiedades físicas. Ambas con autofix.",[17,649,650,655,656,659,660,572],{},[43,651,652],{},[24,653,654],{},"enforce-consistent-variable-syntax"," normaliza la sintaxis de variables CSS entre ",[24,657,658],{},"bg-[var(--primary)]"," y la shorthand de v4 ",[24,661,662],{},"bg-(--primary)",[17,664,665,670,671,561,674,677,678,681],{},[43,666,667],{},[24,668,669],{},"enforce-canonical"," convierte valores arbitrarios a clases nativas cuando existen: ",[24,672,673],{},"p-[2px]",[24,675,676],{},"p-0.5"," (usa ",[24,679,680],{},"rootFontSize"," de 16px por defecto para la conversión). Funciona directo con la API de canonicalización de Tailwind.",[17,683,684,689,690,695,696,561,699,702,703,708],{},[43,685,686],{},[24,687,688],{},"enforce-consistent-important-position"," (default suffix, la forma canónica de v4), ",[43,691,692],{},[24,693,694],{},"enforce-negative-arbitrary-values"," (",[24,697,698],{},"-top-[5px]",[24,700,701],{},"top-[-5px]",") y ",[43,704,705],{},[24,706,707],{},"consistent-variant-order"," completan las reglas de estilo.",[55,710,712],{"id":711},"complexity-mantener-el-código-manejable","Complexity — Mantener el código manejable",[17,714,715,720],{},[43,716,717],{},[24,718,719],{},"max-class-count"," avisa cuando un elemento supera las 20 clases (configurable). Es la señal de que es hora de extraer un componente.",[17,722,723,728],{},[43,724,725],{},[24,726,727],{},"enforce-consistent-line-wrapping"," controla el largo del string de clases por print width o por cantidad de clases por línea.",[55,730,732],{"id":731},"restrictions-reglas-del-design-system","Restrictions — Reglas del design system",[17,734,735,740,741,744],{},[43,736,737],{},[24,738,739],{},"no-hardcoded-colors"," prohíbe colores hardcodeados como ",[24,742,743],{},"bg-[#ff5733]"," en brackets arbitrarios — el típico atajo que erosiona tu design system.",[17,746,747,565,752,757,758,761,762,572],{},[43,748,749],{},[24,750,751],{},"no-arbitrary-value",[43,753,754],{},[24,755,756],{},"no-unnecessary-arbitrary-value"," (con autofix) controlan el uso de valores arbitrarios. La segunda detecta cuando usas ",[24,759,760],{},"h-[auto]"," pero existe ",[24,763,764],{},"h-auto",[17,766,767,772],{},[43,768,769],{},[24,770,771],{},"no-restricted-classes"," permite bloquear clases específicas por nombre o regex, con mensajes custom.",[12,774,776],{"id":775},"extracción-de-clases","Extracción de clases",[17,778,779,780,783],{},"El parser es lo que hace que todo esto funcione de manera confiable. No es un regex que busca ",[24,781,782],{},"className="," y reza. Extrae clases de:",[785,786,787,798,808,811,814,836,841,847,853,860,874],"ul",{},[788,789,790,791,65,794,797],"li",{},"Atributos JSX (",[24,792,793],{},"className",[24,795,796],{},"class",")",[788,799,800,801,804,805],{},"Atributos JSX con objetos — e.g. el prop ",[24,802,803],{},"classNames"," de Mantine: ",[24,806,807],{},"\u003CInput classNames={{ root: \"flex\", input: \"border-none\" }} \u002F>",[788,809,810],{},"Template literals con interpolación",[788,812,813],{},"Ternarios",[788,815,816,817,65,820,65,823,65,826,65,829,65,832,835],{},"Funciones de utilidad: ",[24,818,819],{},"cn()",[24,821,822],{},"clsx()",[24,824,825],{},"cx()",[24,827,828],{},"cva()",[24,830,831],{},"twMerge()",[24,833,834],{},"twJoin()",", y más",[788,837,838,840],{},[24,839,828],{}," completo — base, variants, compoundVariants",[788,842,843,846],{},[24,844,845],{},"tv()"," completo — base, slots, variants con objetos de slots, compoundSlots",[788,848,849,852],{},[24,850,851],{},"classed()"," (tw-classed) — ignora el tipo de elemento, extrae clases y config estilo cva",[788,854,855,856,859],{},"Tagged templates (",[24,857,858],{},"tw\\","...``)",[788,861,862,863,65,865,65,868,65,871,797],{},"Variables por nombre (",[24,864,793],{},[24,866,867],{},"classes",[24,869,870],{},"style",[24,872,873],{},"styles",[788,875,876,877,880],{},"Clases de componentes definidas con ",[24,878,879],{},"@layer components { .btn {} }"," en tu CSS",[17,882,883],{},"Maneja nested brackets, calc anidado, arbitrary variants, quoted values, important modifier, negative values y named groups\u002Fpeers. Los edge cases que rompen otros parsers.",[55,885,887],{"id":886},"detección-customizable","Detección customizable",[17,889,890,891,894,895,897,898,897,900,902,903,906],{},"Por defecto el plugin detecta clases en atributos comunes, 14 funciones de utilidad, tagged templates ",[24,892,893],{},"tw",", y variables con nombres como ",[24,896,793],{},"\u002F",[24,899,867],{},[24,901,870],{},". Puedes extender estos defaults vía ",[24,904,905],{},"settings.tailwindcss"," — todos los valores son aditivos:",[81,908,912],{"className":909,"code":910,"language":911,"meta":86,"style":86},"language-jsonc shiki shiki-themes material-theme-ocean","{\n  \"settings\": {\n    \"tailwindcss\": {\n      \"attributes\": [\"overlayClassName\"],\n      \"callees\": [\"myHelper\"],\n      \"tags\": [\"css\"],\n      \"variablePatterns\": [\"^tw\"],\n    },\n  },\n}\n","jsonc",[24,913,914,918,923,928,933,938,943,948,953,959],{"__ignoreMap":86},[90,915,916],{"class":92,"line":93},[90,917,97],{},[90,919,920],{"class":92,"line":100},[90,921,922],{},"  \"settings\": {\n",[90,924,925],{"class":92,"line":130},[90,926,927],{},"    \"tailwindcss\": {\n",[90,929,930],{"class":92,"line":145},[90,931,932],{},"      \"attributes\": [\"overlayClassName\"],\n",[90,934,935],{"class":92,"line":170},[90,936,937],{},"      \"callees\": [\"myHelper\"],\n",[90,939,940],{"class":92,"line":190},[90,941,942],{},"      \"tags\": [\"css\"],\n",[90,944,945],{"class":92,"line":210},[90,946,947],{},"      \"variablePatterns\": [\"^tw\"],\n",[90,949,950],{"class":92,"line":216},[90,951,952],{},"    },\n",[90,954,956],{"class":92,"line":955},9,[90,957,958],{},"  },\n",[90,960,962],{"class":92,"line":961},10,[90,963,219],{},[17,965,966,967,970],{},"Esto aplica a las 22 reglas de una vez. Si necesitas quitar un default built-in, usa ",[24,968,969],{},"exclude"," en el mismo bloque de settings.",[12,972,974],{"id":973},"la-historia-detrás","La historia detrás",[17,976,977,978,981,982,985],{},"Partí planificando qué quería, el stack que iba a usar y cómo quería que funcionara todo. Después de planificar la implementación con ",[43,979,980],{},"Claude Code",", arrancó la iteración hasta conseguir las 22 reglas actuales. El repo incluye un ",[24,983,984],{},"CLAUDE.md"," y skills configuradas que permiten a cualquier contribuidor usar el mismo workflow para escribir reglas nuevas — la misma herramienta con la que se construyó el plugin. Si quieres agregar una regla, Claude Code ya sabe cómo hacerlo en este proyecto.",[17,987,988,989,996,997,1002,1003,1008,1009,1014,1015,1020],{},"El proyecto corre completamente sobre el ecosistema de herramientas de VoidZero. ",[990,991,995],"a",{"href":992,"rel":993},"https:\u002F\u002Ftsdown.dev\u002F",[994],"nofollow","tsdown"," para el build, ",[990,998,1001],{"href":999,"rel":1000},"https:\u002F\u002Foxc.rs\u002F#feature-formatter",[994],"oxfmt"," para el formateo, ",[990,1004,1007],{"href":1005,"rel":1006},"https:\u002F\u002Fvitest.dev",[994],"vitest"," para testing, ",[990,1010,1013],{"href":1011,"rel":1012},"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftypescript-go",[994],"tsgo"," (TypeScript 7 nativo en Go) para el type checking, y por supuesto ",[990,1016,1019],{"href":1017,"rel":1018},"https:\u002F\u002Foxc.rs\u002F#feature-linter",[994],"oxlint"," para el linting del propio plugin. Cada herramienta en la cadena está construida sobre Rust u optimizada para velocidad.",[17,1022,1023],{},"No fue una decisión cosmética, fue dogfooding deliberado. Si vas a hacer un plugin para oxlint, tiene sentido que todo el toolchain sea del mismo ecosistema. Y si vas a desarrollar con un agente de IA, tiene sentido que el repo esté preparado para ello.",[12,1025,1027],{"id":1026},"cómo-empezar","Cómo empezar",[81,1029,1033],{"className":1030,"code":1031,"language":1032,"meta":86,"style":86},"language-bash shiki shiki-themes material-theme-ocean","pnpm add -D oxlint-tailwindcss\n","bash",[24,1034,1035],{"__ignoreMap":86},[90,1036,1037,1040,1043,1046],{"class":92,"line":93},[90,1038,1039],{"class":151},"pnpm",[90,1041,1042],{"class":121}," add",[90,1044,1045],{"class":121}," -D",[90,1047,1048],{"class":121}," oxlint-tailwindcss\n",[17,1050,1051,1052,113],{},"Agrega el plugin a tu ",[24,1053,1054],{},".oxlintrc.json",[81,1056,1058],{"className":83,"code":1057,"language":85,"meta":86,"style":86},"{\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-duplicate-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fno-deprecated-classes\": \"error\",\n    \"tailwindcss\u002Fno-unnecessary-whitespace\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\",\n    \"tailwindcss\u002Fenforce-shorthand\": \"warn\",\n    \"tailwindcss\u002Fno-hardcoded-colors\": \"warn\"\n    \u002F\u002F...\n  }\n}\n",[24,1059,1060,1064,1084,1096,1114,1133,1151,1170,1189,1207,1226,1244,1250,1255],{"__ignoreMap":86},[90,1061,1062],{"class":92,"line":93},[90,1063,97],{"class":96},[90,1065,1066,1068,1070,1072,1074,1076,1078,1080,1082],{"class":92,"line":100},[90,1067,103],{"class":96},[90,1069,107],{"class":106},[90,1071,110],{"class":96},[90,1073,113],{"class":96},[90,1075,116],{"class":96},[90,1077,110],{"class":96},[90,1079,122],{"class":121},[90,1081,110],{"class":96},[90,1083,127],{"class":96},[90,1085,1086,1088,1090,1092,1094],{"class":92,"line":130},[90,1087,103],{"class":96},[90,1089,135],{"class":106},[90,1091,110],{"class":96},[90,1093,113],{"class":96},[90,1095,142],{"class":96},[90,1097,1098,1100,1102,1104,1106,1108,1110,1112],{"class":92,"line":145},[90,1099,148],{"class":96},[90,1101,152],{"class":151},[90,1103,110],{"class":96},[90,1105,113],{"class":96},[90,1107,159],{"class":96},[90,1109,162],{"class":121},[90,1111,110],{"class":96},[90,1113,167],{"class":96},[90,1115,1116,1118,1121,1123,1125,1127,1129,1131],{"class":92,"line":170},[90,1117,148],{"class":96},[90,1119,1120],{"class":151},"tailwindcss\u002Fno-duplicate-classes",[90,1122,110],{"class":96},[90,1124,113],{"class":96},[90,1126,159],{"class":96},[90,1128,162],{"class":121},[90,1130,110],{"class":96},[90,1132,167],{"class":96},[90,1134,1135,1137,1139,1141,1143,1145,1147,1149],{"class":92,"line":190},[90,1136,148],{"class":96},[90,1138,175],{"class":151},[90,1140,110],{"class":96},[90,1142,113],{"class":96},[90,1144,159],{"class":96},[90,1146,162],{"class":121},[90,1148,110],{"class":96},[90,1150,167],{"class":96},[90,1152,1153,1155,1158,1160,1162,1164,1166,1168],{"class":92,"line":210},[90,1154,148],{"class":96},[90,1156,1157],{"class":151},"tailwindcss\u002Fno-deprecated-classes",[90,1159,110],{"class":96},[90,1161,113],{"class":96},[90,1163,159],{"class":96},[90,1165,162],{"class":121},[90,1167,110],{"class":96},[90,1169,167],{"class":96},[90,1171,1172,1174,1177,1179,1181,1183,1185,1187],{"class":92,"line":216},[90,1173,148],{"class":96},[90,1175,1176],{"class":151},"tailwindcss\u002Fno-unnecessary-whitespace",[90,1178,110],{"class":96},[90,1180,113],{"class":96},[90,1182,159],{"class":96},[90,1184,162],{"class":121},[90,1186,110],{"class":96},[90,1188,167],{"class":96},[90,1190,1191,1193,1195,1197,1199,1201,1203,1205],{"class":92,"line":955},[90,1192,148],{"class":96},[90,1194,195],{"class":151},[90,1196,110],{"class":96},[90,1198,113],{"class":96},[90,1200,159],{"class":96},[90,1202,204],{"class":121},[90,1204,110],{"class":96},[90,1206,167],{"class":96},[90,1208,1209,1211,1214,1216,1218,1220,1222,1224],{"class":92,"line":961},[90,1210,148],{"class":96},[90,1212,1213],{"class":151},"tailwindcss\u002Fenforce-shorthand",[90,1215,110],{"class":96},[90,1217,113],{"class":96},[90,1219,159],{"class":96},[90,1221,204],{"class":121},[90,1223,110],{"class":96},[90,1225,167],{"class":96},[90,1227,1229,1231,1234,1236,1238,1240,1242],{"class":92,"line":1228},11,[90,1230,148],{"class":96},[90,1232,1233],{"class":151},"tailwindcss\u002Fno-hardcoded-colors",[90,1235,110],{"class":96},[90,1237,113],{"class":96},[90,1239,159],{"class":96},[90,1241,204],{"class":121},[90,1243,207],{"class":96},[90,1245,1247],{"class":92,"line":1246},12,[90,1248,1249],{"class":303},"    \u002F\u002F...\n",[90,1251,1253],{"class":92,"line":1252},13,[90,1254,213],{"class":96},[90,1256,1258],{"class":92,"line":1257},14,[90,1259,219],{"class":96},[17,1261,1262],{},"Ejecuta oxlint. Eso es todo.",[12,1264,1266],{"id":1265},"pruébalo","Pruébalo",[17,1268,1269,1270,1275,1276,1281],{},"El plugin es funcional, testeado y usado en producción. Pero un linter se hace mejor con feedback real de proyectos reales.\nSi lo pruebas y encuentras un caso que no maneja bien, abre un ",[990,1271,1274],{"href":1272,"rel":1273},"https:\u002F\u002Fgithub.com\u002Fsergioazoc\u002Foxlint-tailwindcss\u002Fissues",[994],"issue",". Si quieres contribuir una regla, el repo ya está preparado para que iteres con Claude Code desde el primer minuto. Y si simplemente te resultó útil, una estrella en ",[990,1277,1280],{"href":1278,"rel":1279},"https:\u002F\u002Fgithub.com\u002Fsergioazoc\u002Foxlint-tailwindcss",[994],"GitHub"," ayuda a que más gente lo encuentre.",[1283,1284],"hr",{},[17,1286,1287,1292,1293],{},[43,1288,1289],{},[990,1290,1280],{"href":1278,"rel":1291},[994]," · ",[43,1294,1295],{},[990,1296,1299],{"href":1297,"rel":1298},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Foxlint-tailwindcss",[994],"npm",[870,1301,1302],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}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 pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":86,"searchDepth":100,"depth":130,"links":1304},[1305,1306,1309,1315,1318,1319,1320],{"id":14,"depth":100,"text":15},{"id":37,"depth":100,"text":38,"children":1307},[1308],{"id":57,"depth":130,"text":58},{"id":250,"depth":100,"text":251,"children":1310},[1311,1312,1313,1314],{"id":254,"depth":130,"text":255},{"id":589,"depth":130,"text":590},{"id":711,"depth":130,"text":712},{"id":731,"depth":130,"text":732},{"id":775,"depth":100,"text":776,"children":1316},[1317],{"id":886,"depth":130,"text":887},{"id":973,"depth":100,"text":974},{"id":1026,"depth":100,"text":1027},{"id":1265,"depth":100,"text":1266},"2026-03-20","2026-04-13","El plugin de linting más completo para Tailwind CSS, nativo para oxlint. 22 reglas con autofix, zero-config y diseñado desde cero para Tailwind CSS v4.","md",null,{},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba",{"title":6,"description":1323},{"loc":1327},"es\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba",[1332,1333,1334],"tooling","tailwindcss","open-source","oxlint-tailwindcss-linting-plugin-tailwind-needed","5pzYre-fyE3N2Z1wZYmUtI4NZFmIRTfrbBO3Hd1i83Q",[1338,1343],{"title":1339,"path":1340,"stem":1341,"description":1342,"children":-1},"Lo que aprendí construyendo Design Systems y que haría diferente","\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente","es\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente","Lecciones reales construyendo Design Systems en Vue y TypeScript: errores comunes, decisiones de arquitectura y qué haría diferente hoy.",{"title":1344,"path":1345,"stem":1346,"description":1347,"children":-1},"Screaming Architecture: la clave para un frontend escalable","\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable","es\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable","Tu proyecto frontend debería gritar lo que hace, no con qué está hecho. Ejemplos concretos de migración, convivencia con frameworks como Nuxt y manejo de shared state.",{"id":1349,"title":1350,"author":7,"body":1351,"date":1321,"dateModified":1322,"description":2442,"extension":1324,"img":1325,"meta":2443,"navigation":413,"path":2444,"published":413,"readingTime":170,"seo":2445,"sitemap":2446,"slug":1325,"stem":2447,"tags":2448,"union":1335,"__hash__":2449},"blog_en\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed.md","oxlint-tailwindcss: the linting plugin Tailwind v4 needed",{"type":9,"value":1352,"toc":2424},[1353,1357,1360,1369,1372,1376,1386,1389,1393,1408,1508,1520,1523,1531,1535,1539,1542,1549,1583,1592,1599,1629,1639,1693,1706,1713,1741,1748,1800,1812,1824,1828,1835,1852,1873,1885,1900,1921,1925,1932,1939,1943,1953,1970,1977,1981,1987,2060,2063,2067,2083,2127,2133,2137,2146,2169,2172,2176,2190,2195,2389,2392,2396,2407,2409,2421],[12,1354,1356],{"id":1355},"from-problem-to-open-source-the-perfect-opportunity-to-contribute","From problem to open-source: the perfect opportunity to contribute",[17,1358,1359],{},"The day the oxc team opened the alpha for native oxlint plugins, it was the perfect excuse to solve a problem I'd been having at my current job for a while — linting Tailwind CSS classes with oxlint.",[17,1361,1362,1363,1365,1366,1368],{},"If you use Tailwind CSS v4 with oxlint, the existing linting options aren't built for that combo. ",[24,1364,26],{}," is solid but lives in the ESLint world and its v4 support is still partial. ",[24,1367,30],{}," works in oxlint through the jsPlugins compatibility layer, and it gets the job done — but it's not a native plugin and its rules are more limited. Neither was designed specifically for oxlint + Tailwind CSS v4.",[17,1370,1371],{},"So I built it.",[12,1373,1375],{"id":1374},"what-is-oxlint-tailwindcss","What is oxlint-tailwindcss",[17,1377,1378,1379,1382,1383,1385],{},"A ",[43,1380,1381],{},"native"," oxlint plugin with 22 linting rules designed exclusively for Tailwind CSS v4. It's not an ESLint port or a wrapper, it uses the ",[24,1384,49],{}," API directly. Only 2 runtime dependencies.",[17,1387,1388],{},"This matters because being native means it shares the same parsing cycle as oxlint. There's no interoperability overhead, no translation layer. It's as fast as oxlint itself, and it shows.",[55,1390,1392],{"id":1391},"works-without-configuration","Works without configuration",[17,1394,1395,1396,65,1398,65,1400,65,1402,1404,1405,1407],{},"The plugin auto-detects your Tailwind CSS entry point. If your file is called ",[24,1397,64],{},[24,1399,68],{},[24,1401,71],{},[24,1403,74],{}," (or any of the conventional names) and contains ",[24,1406,78],{},", it finds it automatically.",[81,1409,1410],{"className":83,"code":84,"language":85,"meta":86,"style":86},[24,1411,1412,1416,1436,1448,1466,1484,1500,1504],{"__ignoreMap":86},[90,1413,1414],{"class":92,"line":93},[90,1415,97],{"class":96},[90,1417,1418,1420,1422,1424,1426,1428,1430,1432,1434],{"class":92,"line":100},[90,1419,103],{"class":96},[90,1421,107],{"class":106},[90,1423,110],{"class":96},[90,1425,113],{"class":96},[90,1427,116],{"class":96},[90,1429,110],{"class":96},[90,1431,122],{"class":121},[90,1433,110],{"class":96},[90,1435,127],{"class":96},[90,1437,1438,1440,1442,1444,1446],{"class":92,"line":130},[90,1439,103],{"class":96},[90,1441,135],{"class":106},[90,1443,110],{"class":96},[90,1445,113],{"class":96},[90,1447,142],{"class":96},[90,1449,1450,1452,1454,1456,1458,1460,1462,1464],{"class":92,"line":145},[90,1451,148],{"class":96},[90,1453,152],{"class":151},[90,1455,110],{"class":96},[90,1457,113],{"class":96},[90,1459,159],{"class":96},[90,1461,162],{"class":121},[90,1463,110],{"class":96},[90,1465,167],{"class":96},[90,1467,1468,1470,1472,1474,1476,1478,1480,1482],{"class":92,"line":170},[90,1469,148],{"class":96},[90,1471,175],{"class":151},[90,1473,110],{"class":96},[90,1475,113],{"class":96},[90,1477,159],{"class":96},[90,1479,162],{"class":121},[90,1481,110],{"class":96},[90,1483,167],{"class":96},[90,1485,1486,1488,1490,1492,1494,1496,1498],{"class":92,"line":190},[90,1487,148],{"class":96},[90,1489,195],{"class":151},[90,1491,110],{"class":96},[90,1493,113],{"class":96},[90,1495,159],{"class":96},[90,1497,204],{"class":121},[90,1499,207],{"class":96},[90,1501,1502],{"class":92,"line":210},[90,1503,213],{"class":96},[90,1505,1506],{"class":92,"line":216},[90,1507,219],{"class":96},[17,1509,1510,1511,1513,1514,1516,1517,1519],{},"The auto-detection follows ",[24,1512,225],{}," statements one level deep — including package imports like ",[24,1515,229],{},". In monorepos, the search stops at ",[24,1518,233],{}," boundaries so each package resolves its own design system automatically.",[17,1521,1522],{},"The design system is loaded once per entry point, cached in memory and on disk. In a monorepo with multiple packages sharing the same entry point, the design system is loaded only once.",[239,1524,1525],{},[17,1526,1527,1528,1530],{},"If you have a non-conventional entry point you can set it in ",[24,1529,246],{}," or per rule.",[12,1532,1534],{"id":1533},"_22-rules-in-four-categories","22 rules in four categories",[55,1536,1538],{"id":1537},"correctness-catch-real-bugs","Correctness — Catch real bugs",[17,1540,1541],{},"Correctness rules catch bugs before they reach the browser.",[17,1543,1544,1548],{},[43,1545,1546],{},[24,1547,265],{}," detects classes that don't exist in your design system and suggests fixes for typos:",[81,1550,1551],{"className":269,"code":270,"language":271,"meta":86,"style":86},[24,1552,1553,1571,1575,1579],{"__ignoreMap":86},[90,1554,1555,1557,1559,1561,1563,1565,1567,1569],{"class":92,"line":93},[90,1556,278],{"class":96},[90,1558,282],{"class":281},[90,1560,285],{"class":106},[90,1562,288],{"class":96},[90,1564,110],{"class":96},[90,1566,293],{"class":121},[90,1568,110],{"class":96},[90,1570,298],{"class":96},[90,1572,1573],{"class":92,"line":100},[90,1574,304],{"class":303},[90,1576,1577],{"class":92,"line":130},[90,1578,309],{"class":303},[90,1580,1581],{"class":92,"line":145},[90,1582,314],{"class":303},[17,1584,1585,1586,1588,1589,1591],{},"It supports ",[24,1587,320],{}," to allow custom classes not in your design system and ",[24,1590,324],{}," to skip prefixes that aren't Tailwind classes.",[17,1593,1594,1598],{},[43,1595,1596],{},[24,1597,332],{}," tells you exactly which CSS property is conflicting and which class wins:",[81,1600,1601],{"className":269,"code":336,"language":271,"meta":86,"style":86},[24,1602,1603,1621,1625],{"__ignoreMap":86},[90,1604,1605,1607,1609,1611,1613,1615,1617,1619],{"class":92,"line":93},[90,1606,278],{"class":96},[90,1608,282],{"class":281},[90,1610,285],{"class":106},[90,1612,288],{"class":96},[90,1614,110],{"class":96},[90,1616,353],{"class":121},[90,1618,110],{"class":96},[90,1620,298],{"class":96},[90,1622,1623],{"class":92,"line":100},[90,1624,362],{"class":303},[90,1626,1627],{"class":92,"line":130},[90,1628,367],{"class":303},[17,1630,1631,1635,1636,1638],{},[43,1632,1633],{},[24,1634,374],{}," detects when you use ",[24,1637,378],{}," without a base class, something that often causes missing styles in light mode:",[81,1640,1642],{"className":269,"code":1641,"language":271,"meta":86,"style":86},"\u002F\u002F ❌ — what background does it have in light mode?\n\u003Cdiv className=\"dark:bg-gray-900\" \u002F>\n\n\u002F\u002F ✅\n\u003Cdiv className=\"bg-white dark:bg-gray-900\" \u002F>\n",[24,1643,1644,1649,1667,1671,1675],{"__ignoreMap":86},[90,1645,1646],{"class":92,"line":93},[90,1647,1648],{"class":303},"\u002F\u002F ❌ — what background does it have in light mode?\n",[90,1650,1651,1653,1655,1657,1659,1661,1663,1665],{"class":92,"line":100},[90,1652,278],{"class":96},[90,1654,282],{"class":281},[90,1656,285],{"class":106},[90,1658,288],{"class":96},[90,1660,110],{"class":96},[90,1662,404],{"class":121},[90,1664,110],{"class":96},[90,1666,298],{"class":96},[90,1668,1669],{"class":92,"line":130},[90,1670,414],{"emptyLinePlaceholder":413},[90,1672,1673],{"class":92,"line":145},[90,1674,419],{"class":303},[90,1676,1677,1679,1681,1683,1685,1687,1689,1691],{"class":92,"line":170},[90,1678,278],{"class":96},[90,1680,282],{"class":281},[90,1682,285],{"class":106},[90,1684,288],{"class":96},[90,1686,110],{"class":96},[90,1688,434],{"class":121},[90,1690,110],{"class":96},[90,1692,298],{"class":96},[17,1694,1695,1699,1700,1702,1703,1705],{},[43,1696,1697],{},[24,1698,374],{}," checks ",[24,1701,378],{}," by default, but the ",[24,1704,451],{}," option lets you enforce the same pattern for any variant — useful if your project uses custom variants.",[17,1707,1708,1712],{},[43,1709,1710],{},[24,1711,459],{}," catches redundant variants where the base class already applies unconditionally:",[81,1714,1716],{"className":269,"code":1715,"language":271,"meta":86,"style":86},"\u002F\u002F ❌ — dark:flex is redundant, flex already applies always\n\u003Cdiv className=\"flex dark:flex\" \u002F>\n",[24,1717,1718,1723],{"__ignoreMap":86},[90,1719,1720],{"class":92,"line":93},[90,1721,1722],{"class":303},"\u002F\u002F ❌ — dark:flex is redundant, flex already applies always\n",[90,1724,1725,1727,1729,1731,1733,1735,1737,1739],{"class":92,"line":100},[90,1726,278],{"class":96},[90,1728,282],{"class":281},[90,1730,285],{"class":106},[90,1732,288],{"class":96},[90,1734,110],{"class":96},[90,1736,485],{"class":121},[90,1738,110],{"class":96},[90,1740,298],{"class":96},[17,1742,1743,1747],{},[43,1744,1745],{},[24,1746,496],{}," automatically replaces classes deprecated in v4:",[81,1749,1750],{"className":269,"code":500,"language":271,"meta":86,"style":86},[24,1751,1752,1756,1774,1778,1782],{"__ignoreMap":86},[90,1753,1754],{"class":92,"line":93},[90,1755,507],{"class":303},[90,1757,1758,1760,1762,1764,1766,1768,1770,1772],{"class":92,"line":100},[90,1759,278],{"class":96},[90,1761,282],{"class":281},[90,1763,285],{"class":106},[90,1765,288],{"class":96},[90,1767,110],{"class":96},[90,1769,522],{"class":121},[90,1771,110],{"class":96},[90,1773,298],{"class":96},[90,1775,1776],{"class":92,"line":130},[90,1777,414],{"emptyLinePlaceholder":413},[90,1779,1780],{"class":92,"line":145},[90,1781,535],{"class":303},[90,1783,1784,1786,1788,1790,1792,1794,1796,1798],{"class":92,"line":170},[90,1785,278],{"class":96},[90,1787,282],{"class":281},[90,1789,285],{"class":106},[90,1791,288],{"class":96},[90,1793,110],{"class":96},[90,1795,550],{"class":121},[90,1797,110],{"class":96},[90,1799,298],{"class":96},[17,1801,1802,1803,561,1805,1807,1808,561,1810,572],{},"Also ",[24,1804,560],{},[24,1806,564],{}," and ",[24,1809,568],{},[24,1811,571],{},[17,1813,1814,1815,1819,1820,572],{},"Plus the usual ",[43,1816,1817],{},[24,1818,580],{}," (with autofix) and ",[43,1821,1822],{},[24,1823,586],{},[55,1825,1827],{"id":1826},"style-team-consistency","Style — Team consistency",[17,1829,1830,1834],{},[43,1831,1832],{},[24,1833,597],{}," sorts classes according to the official Tailwind CSS order (with autofix), compatible with oxfmt and prettier-plugin-tailwindcss. Its strict mode groups classes by variant prefix, sorts within each group, and orders groups by variant priority.",[17,1836,1837,1841,1842,1844,1845,65,1847,1844,1849,1851],{},[43,1838,1839],{},[24,1840,605],{}," converts ",[24,1843,609],{}," to ",[24,1846,613],{},[24,1848,616],{},[24,1850,619],{},", and many more combinations. All with autofix.",[17,1853,1854,1858,1859,561,1861,65,1863,561,1865,1867,1868,1872],{},[43,1855,1856],{},[24,1857,627],{}," converts physical properties to logical ones for LTR\u002FRTL support: ",[24,1860,631],{},[24,1862,634],{},[24,1864,637],{},[24,1866,640],{},". Its inverse, ",[43,1869,1870],{},[24,1871,646],{},", does the opposite for projects that are LTR-only and prefer consistency with physical properties. Both with autofix.",[17,1874,1875,1879,1880,1882,1883,572],{},[43,1876,1877],{},[24,1878,654],{}," normalizes CSS variable syntax between ",[24,1881,658],{}," and v4's shorthand ",[24,1884,662],{},[17,1886,1887,1891,1892,561,1894,1896,1897,1899],{},[43,1888,1889],{},[24,1890,669],{}," converts arbitrary values to native classes when they exist: ",[24,1893,673],{},[24,1895,676],{}," (uses ",[24,1898,680],{}," of 16px by default for conversion). It plugs directly into Tailwind's canonicalization API.",[17,1901,1902,1906,1907,695,1911,561,1913,1915,1916,1920],{},[43,1903,1904],{},[24,1905,688],{}," (default suffix, v4's canonical form), ",[43,1908,1909],{},[24,1910,694],{},[24,1912,698],{},[24,1914,701],{},"), and ",[43,1917,1918],{},[24,1919,707],{}," round out the style rules.",[55,1922,1924],{"id":1923},"complexity-keep-code-manageable","Complexity — Keep code manageable",[17,1926,1927,1931],{},[43,1928,1929],{},[24,1930,719],{}," warns when an element exceeds 20 classes (configurable). It's the signal that it's time to extract a component.",[17,1933,1934,1938],{},[43,1935,1936],{},[24,1937,727],{}," controls the class string length by print width or by number of classes per line.",[55,1940,1942],{"id":1941},"restrictions-design-system-rules","Restrictions — Design system rules",[17,1944,1945,1949,1950,1952],{},[43,1946,1947],{},[24,1948,739],{}," forbids hardcoded colors like ",[24,1951,743],{}," in arbitrary brackets — the typical shortcut that erodes your design system.",[17,1954,1955,1807,1959,1963,1964,1966,1967,1969],{},[43,1956,1957],{},[24,1958,751],{},[43,1960,1961],{},[24,1962,756],{}," (with autofix) control the use of arbitrary values. The latter detects when you use ",[24,1965,760],{}," but ",[24,1968,764],{}," exists.",[17,1971,1972,1976],{},[43,1973,1974],{},[24,1975,771],{}," allows blocking specific classes by name or regex, with custom messages.",[12,1978,1980],{"id":1979},"class-extraction","Class extraction",[17,1982,1983,1984,1986],{},"The parser is what makes all of this work reliably. It's not a regex that looks for ",[24,1985,782],{}," and hopes for the best. It extracts classes from:",[785,1988,1989,1996,2001,2004,2007,2023,2029,2034,2039,2043,2054],{},[788,1990,1991,1992,65,1994,797],{},"JSX attributes (",[24,1993,793],{},[24,1995,796],{},[788,1997,1998,1999],{},"Object-valued JSX attributes — e.g. Mantine's ",[24,2000,807],{},[788,2002,2003],{},"Template literals with interpolation",[788,2005,2006],{},"Ternaries",[788,2008,2009,2010,65,2012,65,2014,65,2016,65,2018,65,2020,2022],{},"Utility functions: ",[24,2011,819],{},[24,2013,822],{},[24,2015,825],{},[24,2017,828],{},[24,2019,831],{},[24,2021,834],{},", and more",[788,2024,2025,2026,2028],{},"Full ",[24,2027,828],{}," — base, variants, compoundVariants",[788,2030,2025,2031,2033],{},[24,2032,845],{}," — base, slots, variants with slot objects, compoundSlots",[788,2035,2036,2038],{},[24,2037,851],{}," (tw-classed) — skips element type, extracts classes and cva-like config",[788,2040,855,2041,859],{},[24,2042,858],{},[788,2044,2045,2046,65,2048,65,2050,65,2052,797],{},"Variables by name (",[24,2047,793],{},[24,2049,867],{},[24,2051,870],{},[24,2053,873],{},[788,2055,2056,2057,2059],{},"Component classes defined with ",[24,2058,879],{}," in your CSS",[17,2061,2062],{},"It handles nested brackets, nested calc, arbitrary variants, quoted values, important modifier, negative values, and named groups\u002Fpeers. The edge cases that break other parsers.",[55,2064,2066],{"id":2065},"custom-class-detection","Custom class detection",[17,2068,2069,2070,2072,2073,897,2075,897,2077,2079,2080,2082],{},"By default the plugin detects classes in common attributes, 14 utility functions, ",[24,2071,893],{}," tagged templates, and variables named ",[24,2074,793],{},[24,2076,867],{},[24,2078,870],{},". You can extend these defaults via ",[24,2081,905],{}," — all values are additive:",[81,2084,2085],{"className":909,"code":910,"language":911,"meta":86,"style":86},[24,2086,2087,2091,2095,2099,2103,2107,2111,2115,2119,2123],{"__ignoreMap":86},[90,2088,2089],{"class":92,"line":93},[90,2090,97],{},[90,2092,2093],{"class":92,"line":100},[90,2094,922],{},[90,2096,2097],{"class":92,"line":130},[90,2098,927],{},[90,2100,2101],{"class":92,"line":145},[90,2102,932],{},[90,2104,2105],{"class":92,"line":170},[90,2106,937],{},[90,2108,2109],{"class":92,"line":190},[90,2110,942],{},[90,2112,2113],{"class":92,"line":210},[90,2114,947],{},[90,2116,2117],{"class":92,"line":216},[90,2118,952],{},[90,2120,2121],{"class":92,"line":955},[90,2122,958],{},[90,2124,2125],{"class":92,"line":961},[90,2126,219],{},[17,2128,2129,2130,2132],{},"This applies to all 22 rules at once. If you need to remove a built-in default, use ",[24,2131,969],{}," in the same settings block.",[12,2134,2136],{"id":2135},"the-story-behind-it","The story behind it",[17,2138,2139,2140,2142,2143,2145],{},"I started by planning what I wanted, the stack I was going to use, and how I wanted everything to work. After planning the implementation with ",[43,2141,980],{},", the iteration began until reaching the current 22 rules. The repo includes a ",[24,2144,984],{}," and configured skills that allow any contributor to use the same workflow to write new rules — the same tool the plugin was built with. If you want to add a rule, Claude Code already knows how to do it in this project.",[17,2147,2148,2149,2152,2153,2156,2157,2160,2161,2164,2165,2168],{},"The project runs entirely on the VoidZero tool ecosystem. ",[990,2150,995],{"href":992,"rel":2151},[994]," for the build, ",[990,2154,1001],{"href":999,"rel":2155},[994]," for formatting, ",[990,2158,1007],{"href":1005,"rel":2159},[994]," for testing, ",[990,2162,1013],{"href":1011,"rel":2163},[994]," (native TypeScript 7 in Go) for type checking, and of course ",[990,2166,1019],{"href":1017,"rel":2167},[994]," for linting the plugin itself. Every tool in the chain is built on Rust or optimized for speed.",[17,2170,2171],{},"It wasn't a cosmetic decision, it was deliberate dogfooding. If you're going to make a plugin for oxlint, it makes sense that the entire toolchain is from the same ecosystem. And if you're going to develop with an AI agent, it makes sense that the repo is prepared for it.",[12,2173,2175],{"id":2174},"getting-started","Getting started",[81,2177,2178],{"className":1030,"code":1031,"language":1032,"meta":86,"style":86},[24,2179,2180],{"__ignoreMap":86},[90,2181,2182,2184,2186,2188],{"class":92,"line":93},[90,2183,1039],{"class":151},[90,2185,1042],{"class":121},[90,2187,1045],{"class":121},[90,2189,1048],{"class":121},[17,2191,2192,2193,113],{},"Add the plugin to your ",[24,2194,1054],{},[81,2196,2197],{"className":83,"code":1057,"language":85,"meta":86,"style":86},[24,2198,2199,2203,2223,2235,2253,2271,2289,2307,2325,2343,2361,2377,2381,2385],{"__ignoreMap":86},[90,2200,2201],{"class":92,"line":93},[90,2202,97],{"class":96},[90,2204,2205,2207,2209,2211,2213,2215,2217,2219,2221],{"class":92,"line":100},[90,2206,103],{"class":96},[90,2208,107],{"class":106},[90,2210,110],{"class":96},[90,2212,113],{"class":96},[90,2214,116],{"class":96},[90,2216,110],{"class":96},[90,2218,122],{"class":121},[90,2220,110],{"class":96},[90,2222,127],{"class":96},[90,2224,2225,2227,2229,2231,2233],{"class":92,"line":130},[90,2226,103],{"class":96},[90,2228,135],{"class":106},[90,2230,110],{"class":96},[90,2232,113],{"class":96},[90,2234,142],{"class":96},[90,2236,2237,2239,2241,2243,2245,2247,2249,2251],{"class":92,"line":145},[90,2238,148],{"class":96},[90,2240,152],{"class":151},[90,2242,110],{"class":96},[90,2244,113],{"class":96},[90,2246,159],{"class":96},[90,2248,162],{"class":121},[90,2250,110],{"class":96},[90,2252,167],{"class":96},[90,2254,2255,2257,2259,2261,2263,2265,2267,2269],{"class":92,"line":170},[90,2256,148],{"class":96},[90,2258,1120],{"class":151},[90,2260,110],{"class":96},[90,2262,113],{"class":96},[90,2264,159],{"class":96},[90,2266,162],{"class":121},[90,2268,110],{"class":96},[90,2270,167],{"class":96},[90,2272,2273,2275,2277,2279,2281,2283,2285,2287],{"class":92,"line":190},[90,2274,148],{"class":96},[90,2276,175],{"class":151},[90,2278,110],{"class":96},[90,2280,113],{"class":96},[90,2282,159],{"class":96},[90,2284,162],{"class":121},[90,2286,110],{"class":96},[90,2288,167],{"class":96},[90,2290,2291,2293,2295,2297,2299,2301,2303,2305],{"class":92,"line":210},[90,2292,148],{"class":96},[90,2294,1157],{"class":151},[90,2296,110],{"class":96},[90,2298,113],{"class":96},[90,2300,159],{"class":96},[90,2302,162],{"class":121},[90,2304,110],{"class":96},[90,2306,167],{"class":96},[90,2308,2309,2311,2313,2315,2317,2319,2321,2323],{"class":92,"line":216},[90,2310,148],{"class":96},[90,2312,1176],{"class":151},[90,2314,110],{"class":96},[90,2316,113],{"class":96},[90,2318,159],{"class":96},[90,2320,162],{"class":121},[90,2322,110],{"class":96},[90,2324,167],{"class":96},[90,2326,2327,2329,2331,2333,2335,2337,2339,2341],{"class":92,"line":955},[90,2328,148],{"class":96},[90,2330,195],{"class":151},[90,2332,110],{"class":96},[90,2334,113],{"class":96},[90,2336,159],{"class":96},[90,2338,204],{"class":121},[90,2340,110],{"class":96},[90,2342,167],{"class":96},[90,2344,2345,2347,2349,2351,2353,2355,2357,2359],{"class":92,"line":961},[90,2346,148],{"class":96},[90,2348,1213],{"class":151},[90,2350,110],{"class":96},[90,2352,113],{"class":96},[90,2354,159],{"class":96},[90,2356,204],{"class":121},[90,2358,110],{"class":96},[90,2360,167],{"class":96},[90,2362,2363,2365,2367,2369,2371,2373,2375],{"class":92,"line":1228},[90,2364,148],{"class":96},[90,2366,1233],{"class":151},[90,2368,110],{"class":96},[90,2370,113],{"class":96},[90,2372,159],{"class":96},[90,2374,204],{"class":121},[90,2376,207],{"class":96},[90,2378,2379],{"class":92,"line":1246},[90,2380,1249],{"class":303},[90,2382,2383],{"class":92,"line":1252},[90,2384,213],{"class":96},[90,2386,2387],{"class":92,"line":1257},[90,2388,219],{"class":96},[17,2390,2391],{},"Run oxlint. That's it.",[12,2393,2395],{"id":2394},"try-it","Try it",[17,2397,2398,2399,2402,2403,2406],{},"The plugin is functional, tested, and used in production. But a linter gets better with real feedback from real projects.\nIf you try it and find a case it doesn't handle well, open an ",[990,2400,1274],{"href":1272,"rel":2401},[994],". If you want to contribute a rule, the repo is already set up so you can iterate with Claude Code from minute one. And if it was simply useful to you, a star on ",[990,2404,1280],{"href":1278,"rel":2405},[994]," helps more people find it.",[1283,2408],{},[17,2410,2411,1292,2416],{},[43,2412,2413],{},[990,2414,1280],{"href":1278,"rel":2415},[994],[43,2417,2418],{},[990,2419,1299],{"href":1297,"rel":2420},[994],[870,2422,2423],{},"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 pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":86,"searchDepth":100,"depth":130,"links":2425},[2426,2427,2430,2436,2439,2440,2441],{"id":1355,"depth":100,"text":1356},{"id":1374,"depth":100,"text":1375,"children":2428},[2429],{"id":1391,"depth":130,"text":1392},{"id":1533,"depth":100,"text":1534,"children":2431},[2432,2433,2434,2435],{"id":1537,"depth":130,"text":1538},{"id":1826,"depth":130,"text":1827},{"id":1923,"depth":130,"text":1924},{"id":1941,"depth":130,"text":1942},{"id":1979,"depth":100,"text":1980,"children":2437},[2438],{"id":2065,"depth":130,"text":2066},{"id":2135,"depth":100,"text":2136},{"id":2174,"depth":100,"text":2175},{"id":2394,"depth":100,"text":2395},"The most complete linting plugin for Tailwind CSS, native to oxlint. 22 rules with autofix, zero-config and designed from scratch for Tailwind CSS v4.",{},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed",{"title":1350,"description":2442},{"loc":2444},"en\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed",[1332,1333,1334],"s6Dy3yJrbSCt6OdbWMBmBodczFyy3dskwxAxsvAU0qY",1777609840993]