<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oğuzhan Ayyıldız</title>
    <description>The latest articles on DEV Community by Oğuzhan Ayyıldız (@oguzhanayyldz).</description>
    <link>https://dev.to/oguzhanayyldz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4168741%2F1e86e42b-076c-418c-bcd6-ef067f656c08.jpg</url>
      <title>DEV Community: Oğuzhan Ayyıldız</title>
      <link>https://dev.to/oguzhanayyldz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oguzhanayyldz"/>
    <language>en</language>
    <item>
      <title>Why React components from npm render unstyled with Tailwind v3</title>
      <dc:creator>Oğuzhan Ayyıldız</dc:creator>
      <pubDate>Wed, 07 Oct 2026 11:43:14 +0000</pubDate>
      <link>https://dev.to/oguzhanayyldz/why-react-components-from-npm-render-unstyled-with-tailwind-v3-4ci1</link>
      <guid>https://dev.to/oguzhanayyldz/why-react-components-from-npm-render-unstyled-with-tailwind-v3-4ci1</guid>
      <description>&lt;p&gt;Install a React component library, import a &lt;code&gt;Button&lt;/code&gt;, render it. The label shows up and the click handler fires, but the thing looks like a gray browser default. The console is quiet and the build is green.&lt;/p&gt;

&lt;p&gt;I reproduced this in clean projects while working on MoonUI, a component library built on Radix and Tailwind. The components were not broken. My Tailwind setup just had no idea they existed, and then no idea what their colors meant. Those are two separate problems with two separate fixes, and a third small trap sits right behind them.&lt;/p&gt;

&lt;p&gt;Everything here is the Tailwind v3 config format. I have not tried any of it on v4.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailwind only writes the classes it can see
&lt;/h3&gt;

&lt;p&gt;Tailwind v3 does not ship a giant stylesheet. At build time it reads the files listed under &lt;code&gt;content&lt;/code&gt;, collects anything that looks like a class name, and generates CSS for those names only. A library that ships its Tailwind classes inside compiled JavaScript puts them in &lt;code&gt;node_modules&lt;/code&gt;, and your config does not look there unless you tell it to.&lt;/p&gt;

&lt;p&gt;So the first fix is a single extra glob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./node_modules/@moontra/moonui/dist/**/*.{js,mjs}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at &lt;code&gt;dist&lt;/code&gt;, because that is what your bundler actually imports. The source folder is not the thing that gets scanned in your build.&lt;/p&gt;

&lt;p&gt;You could ask why the library's own preset doesn't add this path for you. It leaves &lt;code&gt;content&lt;/code&gt; out on purpose. Paths depend on where your config lives and how your package manager lays out &lt;code&gt;node_modules&lt;/code&gt;, and a wrong guess produces no error at all. One explicit line in your own config is easier to debug than a guess hidden inside a preset.&lt;/p&gt;

&lt;h3&gt;
  
  
  The class exists, but the color does not
&lt;/h3&gt;

&lt;p&gt;Once the glob is in, something changes: padding and rounded corners show up. In my clean-project run the button went from a 24px-high element with no padding to a 40px-high one with 16px of horizontal padding. The background, though, stayed transparent.&lt;/p&gt;

&lt;p&gt;That is because a themeable library rarely hardcodes a color. A button asks for &lt;code&gt;bg-primary&lt;/code&gt;, and Tailwind has to be told what &lt;code&gt;primary&lt;/code&gt; means. Two pieces carry that meaning. The preset maps Tailwind's theme keys onto CSS variables, for example &lt;code&gt;primary&lt;/code&gt; to &lt;code&gt;hsl(var(--primary) / &amp;lt;alpha-value&amp;gt;)&lt;/code&gt;. A token stylesheet then defines &lt;code&gt;--primary&lt;/code&gt; and the rest, for light and dark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@moontra/moonui/tailwind-preset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./node_modules/@moontra/moonui/dist/**/*.{js,mjs}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tailwindcss-animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tailwindcss-animate&lt;/code&gt; plugin is in there because Radix-driven components such as the dialog use &lt;code&gt;animate-in&lt;/code&gt; and &lt;code&gt;fade-in-0&lt;/code&gt; for their open and close states. It is not a dependency of the package, so you install it yourself.&lt;/p&gt;

&lt;p&gt;The part that is easy to miss: after adding only the preset, I checked the generated CSS. The &lt;code&gt;.bg-primary&lt;/code&gt; rule was there. The &lt;code&gt;--primary:&lt;/code&gt; definition was not. The rule pointed at a variable nobody had defined, so the browser threw the declaration away and the background stayed transparent. The preset is the map and the tokens are the territory, and a map alone gets you nowhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading the tokens
&lt;/h3&gt;

&lt;p&gt;Importing the token file is the last step. The package exposes it as &lt;code&gt;@moontra/moonui/tokens.css&lt;/code&gt;, and the import that worked in every setup I measured is the JavaScript one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/main.jsx (Vite) or app/layout.tsx (Next.js)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@moontra/moonui/tokens.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* globals.css */&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tested this on &lt;code&gt;@moontra/moonui&lt;/code&gt; 6.30.3 from npm with Tailwind 3.4, in three setups: Next.js 15 with its default PostCSS config, Next.js 15 with &lt;code&gt;postcss-import&lt;/code&gt; added, and Vite 5. In all three, the production build passed and the &lt;code&gt;Button&lt;/code&gt; computed a real background color (&lt;code&gt;rgb(16, 103, 244)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The CSS version is less uniform, and I would rather say so than pretend otherwise. Writing &lt;code&gt;@import "@moontra/moonui/tokens.css"&lt;/code&gt; in your stylesheet works on default Next.js and on Vite. It fails on Next.js once &lt;code&gt;postcss-import&lt;/code&gt; is in the pipeline, with a "Failed to find" error. The reason is that &lt;code&gt;postcss-import&lt;/code&gt; does not read a package's &lt;code&gt;exports&lt;/code&gt; map, while the resolvers built into Next and Vite do. If you already use &lt;code&gt;postcss-import&lt;/code&gt;, the JavaScript import is the way around it.&lt;/p&gt;

&lt;p&gt;The longer physical path, &lt;code&gt;@moontra/moonui/src/styles/tokens.css&lt;/code&gt;, is not listed in &lt;code&gt;exports&lt;/code&gt;. It builds only in the &lt;code&gt;postcss-import&lt;/code&gt; case and gets rejected as "not exported" elsewhere. I would not use it.&lt;/p&gt;

&lt;p&gt;Load the tokens once, by one of these routes. I did not measure Tailwind v4 or webpack setups that are not Next.js, so I won't say anything about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The order I check things in now
&lt;/h3&gt;

&lt;p&gt;When a package renders unstyled under Tailwind v3, I go through it in this order, because each step produces a different symptom:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No padding, no radius, a bare browser button: the package's &lt;code&gt;dist&lt;/code&gt; folder is missing from &lt;code&gt;content&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Layout looks right, background transparent: check that the preset is in &lt;code&gt;presets&lt;/code&gt;, then check that the CSS variables are defined anywhere in your bundle.&lt;/li&gt;
&lt;li&gt;Layout and color are fine, but dialogs and popovers appear with no motion: the animate plugin is missing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Looking at the computed style in devtools settles most of this in under a minute. If &lt;code&gt;background-color&lt;/code&gt; resolves to &lt;code&gt;rgba(0, 0, 0, 0)&lt;/code&gt; and the &lt;code&gt;.bg-primary&lt;/code&gt; rule exists, you are on step two.&lt;/p&gt;

&lt;p&gt;The code above comes from the free MIT-licensed package behind MoonUI, at &lt;a href="https://github.com/moonuidotdev/moonui" rel="noopener noreferrer"&gt;https://github.com/moonuidotdev/moonui&lt;/a&gt;. If you maintain a Tailwind-based library, do you ship a compiled stylesheet instead of asking people to edit their config, and what made you choose that?&lt;/p&gt;

</description>
      <category>react</category>
      <category>tailwindcss</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
