<?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: Ryan Bosher</title>
    <description>The latest articles on DEV Community by Ryan Bosher (@boshcode).</description>
    <link>https://dev.to/boshcode</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2037778%2F06ee3967-1d07-443b-aa41-13b65319ff11.jpeg</url>
      <title>DEV Community: Ryan Bosher</title>
      <link>https://dev.to/boshcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/boshcode"/>
    <language>en</language>
    <item>
      <title>Build a React + TailwindCSS component library with tsdown</title>
      <dc:creator>Ryan Bosher</dc:creator>
      <pubDate>Sun, 26 Oct 2025 00:15:24 +0000</pubDate>
      <link>https://dev.to/boshcode/build-a-react-tailwindcss-component-library-with-tsdown-1mg7</link>
      <guid>https://dev.to/boshcode/build-a-react-tailwindcss-component-library-with-tsdown-1mg7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bosher.co.nz/blog/tsdown-react-tailwindcss-template" rel="noopener noreferrer"&gt;bosher.co.nz&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Learn how to build tiny, reusable React component libraries styled with TailwindCSS and bundled superfast with &lt;code&gt;tsdown&lt;/code&gt; - all with minimal configuration.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;What you'll build:&lt;/strong&gt; A production-ready React component library with TailwindCSS styling, complete with a playground&lt;br&gt;
for testing, TypeScript declarations, and optimized bundling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to complete:&lt;/strong&gt; 45-60 minutes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate (familiarity with React, node and npm packages recommended)&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;AI Disclaimer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article was written entirely by myself, a human. However, LLMs were used to help with grammar and spelling checks.&lt;/p&gt;







&lt;p&gt;&lt;strong&gt;TL;DR - Quick Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the express version if you're already familiar with the concepts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a project: &lt;code&gt;pnpm dlx create-tsdown@latest your-project-name -t react&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react/jsx-runtime&lt;/code&gt; to the &lt;code&gt;external&lt;/code&gt; array in &lt;code&gt;tsdown.config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install dependencies:
&lt;code&gt;pnpm add -D tailwindcss @bosh-code/tsdown-plugin-inject-css @bosh-code/tsdown-plugin-tailwindcss&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure the plugins in your tsdown config&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;@import "tailwindcss";&lt;/code&gt; to &lt;code&gt;src/index.css&lt;/code&gt; and import it in &lt;code&gt;src/index.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why tsdown?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tsdown.dev/" rel="noopener noreferrer"&gt;tsdown&lt;/a&gt; is the &lt;em&gt;"elegant library bundler"&lt;/em&gt; from &lt;a href="https://voidzero.dev/" rel="noopener noreferrer"&gt;void(0)&lt;/a&gt;, the team&lt;br&gt;
behind &lt;a href="https://vite.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; and &lt;a href="https://rolldown.rs/" rel="noopener noreferrer"&gt;Rolldown&lt;/a&gt;. It's designed specifically for bundling&lt;br&gt;
TypeScript libraries with sensible defaults that eliminate the need for a lot of config overhead.&lt;/p&gt;

&lt;p&gt;Out of the box, tsdown provides React support, TypeScript declaration files, and &lt;code&gt;tsconfig&lt;/code&gt; path resolution - features&lt;br&gt;
that typically require multiple plugins with other bundlers.&lt;/p&gt;
&lt;h3&gt;
  
  
  The CSS challenge
&lt;/h3&gt;

&lt;p&gt;There's one significant limitation: &lt;strong&gt;CSS support&lt;/strong&gt;, particularly for TailwindCSS. By default, tsdown bundles CSS files&lt;br&gt;
but doesn't inject the import statements into your built JavaScript. This means consumers of your library would need to&lt;br&gt;
manually import CSS files - not ideal for component libraries.&lt;/p&gt;

&lt;p&gt;After experimenting with various approaches, I developed two plugins to solve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@bosh-code/tsdown-plugin-inject-css" rel="noopener noreferrer"&gt;&lt;code&gt;@bosh-code/tsdown-plugin-inject-css&lt;/code&gt;&lt;/a&gt; -
Automatically injects CSS imports into built files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@bosh-code/tsdown-plugin-tailwindcss" rel="noopener noreferrer"&gt;&lt;code&gt;@bosh-code/tsdown-plugin-tailwindcss&lt;/code&gt;&lt;/a&gt; -
Processes TailwindCSS (similar to &lt;a href="https://www.npmjs.com/package/@tailwindcss/vite" rel="noopener noreferrer"&gt;&lt;code&gt;@tailwindcss/vite&lt;/code&gt;&lt;/a&gt; but for
tsdown)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@bosh-code/preact-slot" rel="noopener noreferrer"&gt;&lt;code&gt;@bosh-code/preact-slot&lt;/code&gt;&lt;/a&gt; - A copy of &lt;a href="https://www.radix-ui.com/primitives" rel="noopener noreferrer"&gt;
&lt;code&gt;@radix-ui/react-slot&lt;/code&gt;&lt;/a&gt; but for Preact. Created for the &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/preact-shadcn" rel="noopener noreferrer"&gt;
&lt;code&gt;templates/preact-shadcn&lt;/code&gt;&lt;/a&gt;
branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide walks through setting up a component library with full TailwindCSS support, demonstrating both the problem&lt;br&gt;
and the solution. At the end of this guide, there are multiple other ready-to-use templates with additional UI&lt;br&gt;
frameworks that are out of scope of this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (I recommend always using LTS and at least v22 or higher)

&lt;ul&gt;
&lt;li&gt;I recommend &lt;a href="https://asdf-vm.com/" rel="noopener noreferrer"&gt;&lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt; for version management - it handles multiple runtime versions across
projects more elegantly than nvm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pnpm&lt;/strong&gt; for package management

&lt;ul&gt;
&lt;li&gt;Why pnpm? Faster installs, better disk space usage, and stricter dependency resolution than npm or yarn&lt;/li&gt;
&lt;li&gt;The template is configured for pnpm, so using npm or yarn may cause issues&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Following along
&lt;/h3&gt;

&lt;p&gt;You can follow this guide using the &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;,&lt;br&gt;
which contains the complete code with git history showing each step.&lt;/p&gt;

&lt;p&gt;The repo uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://davidregalado255.medium.com/what-is-gitflow-b3396770cd42" rel="noopener noreferrer"&gt;Gitflow&lt;/a&gt; for branching&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://commitlint.js.org/" rel="noopener noreferrer"&gt;commitlint&lt;/a&gt; for conventional commits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look for 🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt; markers with commit hashes throughout this guide to see exactly what changed at each step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up the project
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Creating the initial project
&lt;/h3&gt;

&lt;p&gt;Start by creating a new project using &lt;code&gt;create-tsdown&lt;/code&gt; with the React template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dlx create-tsdown@latest your-project-name &lt;span class="nt"&gt;-t&lt;/span&gt; react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a new tsdown React library project using &lt;a href="https://github.com/Gugustinette/create-tsdown" rel="noopener noreferrer"&gt;create-tsdown&lt;/a&gt;,&lt;br&gt;
an excellent starter tool started by &lt;a href="https://github.com/Gugustinette" rel="noopener noreferrer"&gt;Gugustinette&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── README.md
├── package.json
├── playground          &lt;span class="c"&gt;# Demo app for testing components&lt;/span&gt;
│   ├── index.html
│   └── main.tsx
├── src
│   ├── MyButton.tsx   &lt;span class="c"&gt;# Example component&lt;/span&gt;
│   └── index.ts       &lt;span class="c"&gt;# Library entry point&lt;/span&gt;
├── tests
│   ├── index.test.tsx
│   └── setup.ts
├── tsconfig.json
├── tsdown.config.ts   &lt;span class="c"&gt;# Build configuration&lt;/span&gt;
└── vitest.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/commit/782f6ee771014af6ac91b6eadc96a7ac37c8182b" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;782f6ee&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing dependencies
&lt;/h3&gt;

&lt;p&gt;Next, install the initial dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="c"&gt;# or pnpm i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If prompted about build scripts:&lt;/strong&gt; Run &lt;code&gt;pnpm approve-builds&lt;/code&gt; to allow pnpm to execute post-install scripts for certain&lt;br&gt;
packages. I believe you can skip this, but may as well allow them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update 2025-10-26:&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;I believe the template has been updated to remove the actions, if so, feel free to skip this part&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Update your &lt;code&gt;package.json&lt;/code&gt; with pnpm and node versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  // other properties...
  "packageManager": "pnpm@10.18.1",
  // Your installed pnpm version
  "engines": {
    "node": "&amp;gt;=22"
    // Minimum Node.js version
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Without these fields set, the CI/CD unit&lt;br&gt;
tests &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/actions/runs/18331296689" rel="noopener noreferrer"&gt;will fail&lt;/a&gt;. Setting these&lt;br&gt;
fields is also best practice, but out of scope for this tutorial.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/commit/3ca1f6d59524b908c151f60e8e6247f53a4a63db" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;3ca1f6d&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Playground app
&lt;/h2&gt;

&lt;p&gt;Before jumping into creating components and config changes, let's verify everything works. Run the playground app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm run playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be greeted with the default button component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe4qbcatju7ksxt2orj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe4qbcatju7ksxt2orj5.png" alt="Playground app showing default button component" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; The playground uses Vite to run a development server that imports your library source code&lt;br&gt;
directly, providing hot module reloading for rapid development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code quality
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Installing ESLint
&lt;/h3&gt;

&lt;p&gt;Add the necessary packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @eslint/js eslint eslint-plugin-react globals typescript-eslint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring ESLint
&lt;/h3&gt;

&lt;p&gt;Create an &lt;code&gt;eslint.config.js&lt;/code&gt; config file in your project root:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;eslintJs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@eslint/js&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;globalIgnores&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint/config&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="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-react&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="nx"&gt;globals&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;globals&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="nx"&gt;tseslint&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typescript-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="c1"&gt;// Ignore build output&lt;/span&gt;
  &lt;span class="nf"&gt;globalIgnores&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;

  &lt;span class="c1"&gt;// JS files&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&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="s1"&gt;**/*.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;eslintJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// TS files&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&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="s1"&gt;**/*.js&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="s1"&gt;**/*.ts&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="s1"&gt;**/*.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tseslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// TSX files&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&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="s1"&gt;**/*.ts&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="s1"&gt;**/*.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tseslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parserOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ecmaFeatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;jsx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;globals&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;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;react&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;detect&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;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic linting config for a React + TypeScript project. You can modify it to suit your needs.&lt;/p&gt;

&lt;p&gt;Additionally, add a &lt;code&gt;lint&lt;/code&gt; script to your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "scripts": {
+   "lint": "eslint ."
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxjvrl6zm0h6rhka91vv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxjvrl6zm0h6rhka91vv.png" alt="GitHub action showing test job is passing" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/commit/58590dec76ca36951236f53134b53eb2c702b4ab" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;58590de&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuring tsdown
&lt;/h2&gt;
&lt;h3&gt;
  
  
  A note on peer deps
&lt;/h3&gt;

&lt;p&gt;Component libraries like this should declare React as a &lt;strong&gt;peer dependency&lt;/strong&gt; rather than a regular dependency. This&lt;br&gt;
ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The consuming project controls the React version&lt;/li&gt;
&lt;li&gt;No duplicate React instances&lt;/li&gt;
&lt;li&gt;A smaller bundle size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Move React to &lt;code&gt;peerDependencies&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
+ "peerDependencies": {
+   "react": "^19.1.0"
+ },
  "devDependencies": {
-   "react": "^19.1.0"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going forward, any packages that you add and are imported directly in your &lt;code&gt;/src&lt;/code&gt; files should be added to the&lt;br&gt;
&lt;code&gt;peerDependencies&lt;/code&gt; section.&lt;/p&gt;
&lt;h3&gt;
  
  
  External Dependencies
&lt;/h3&gt;

&lt;p&gt;Next, we need to tell tsdown to treat &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react-dom&lt;/code&gt; as external dependencies, so they are not bundled with the&lt;br&gt;
library. Open &lt;code&gt;tsdown.config.ts&lt;/code&gt; and update the config to include an &lt;code&gt;external&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tsdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&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="s1"&gt;./src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// We don't want to bundle them with the library,&lt;/span&gt;
    &lt;span class="c1"&gt;// as the consuming project will provide them.&lt;/span&gt;
    &lt;span class="na"&gt;external&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="s1"&gt;react&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="s1"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;neutral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;&lt;strong&gt;Why externalize React?&lt;/strong&gt;&lt;br&gt;
Without this configuration, tsdown bundles React into your library's output, causing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bloated bundle size&lt;/li&gt;
&lt;li&gt;React hooks violations from multiple React instances&lt;/li&gt;
&lt;li&gt;Version conflicts in consuming projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see this in action by building your library and checking &lt;code&gt;dist/index.js&lt;/code&gt;. You should see&lt;br&gt;
&lt;code&gt;import React from "react"&lt;/code&gt; at the top and no bundled React section.&lt;/p&gt;
&lt;h3&gt;
  
  
  Optimizing for production
&lt;/h3&gt;

&lt;p&gt;Add these recommended options for production-ready builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tsdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&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="s1"&gt;./src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;external&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="s1"&gt;react&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="s1"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// We know the components will run in a browser environment,&lt;/span&gt;
    &lt;span class="c1"&gt;// so the platform should be 'browser' to enable browser-specific optimizations&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Enable minification for production builds&lt;/span&gt;
    &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate source maps for easier debugging&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;If enabling minify on production builds, make sure to set the &lt;code&gt;NODE_ENV&lt;/code&gt; variable when building. I like to add a&lt;br&gt;
separate script for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  // other properties...
  "scripts": {
    "build": "tsdown",
    "build:prod": "NODE_ENV=prod tsdown",
    // other scripts...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test both build modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build        &lt;span class="c"&gt;# Development build with readable output&lt;/span&gt;
pnpm build:prod   &lt;span class="c"&gt;# Production build with minification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare the &lt;code&gt;dist/&lt;/code&gt; folder sizes - production builds should be significantly smaller.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/commit/5f72580896fb1f352e0a6ad0181c7dc5970f3e9e" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;5f72580&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  (Optional) Switching to Preact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Skip this section if you're sticking with React!&lt;/strong&gt; - Jump to Adding TailwindCSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://preactjs.com/" rel="noopener noreferrer"&gt;Preact&lt;/a&gt; is a lightweight alternative to React that is compatible with the React API. I prefer it&lt;br&gt;
due to its size and the awesome &lt;a href="https://preactjs.com/guide/v10/signals/" rel="noopener noreferrer"&gt;preact/signals&lt;/a&gt; API for state management.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/preact" rel="noopener noreferrer"&gt;repo&lt;/a&gt; includes a complete Preact&lt;br&gt;
template branch you can use instead.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacing React packages
&lt;/h3&gt;

&lt;p&gt;Remove React and install Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm remove react react-dom @types/react @types/react-dom @vitejs/plugin-react
pnpm add &lt;span class="nt"&gt;--save-peer&lt;/span&gt; preact
pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @preact/preset-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "peerDependencies": {
-   "react": "^19.1.0",
+   "preact": "^10.27.2"
  },
  "devDependencies": {
+   "@preact/preset-vite": "^2.10.2",
-   "@types/react": "^19.1.3",
-   "@types/react-dom": "^19.1.4",
-   "@vitejs/plugin-react": "^4.4.1",
-   "react-dom": "^19.1.0"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TypeScript configuration
&lt;/h3&gt;

&lt;p&gt;Configure TypeScript to alias React imports to Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    // other options...
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "rootDir": ".",
    "paths": {
      // These aliases allow React code to work with Preact
      "react": [
        "./node_modules/preact/compat/"
      ],
      "react/jsx-runtime": [
        "./node_modules/preact/jsx-runtime"
      ],
      "react-dom": [
        "./node_modules/preact/compat/"
      ],
      "react-dom/*": [
        "./node_modules/preact/compat/*"
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How this works:&lt;/strong&gt; Preact includes a &lt;code&gt;compat&lt;/code&gt; layer that implements React's API, so existing React code &lt;em&gt;should&lt;/em&gt; work&lt;br&gt;
without changes.&lt;/p&gt;
&lt;h3&gt;
  
  
  ESLint adjustments
&lt;/h3&gt;

&lt;p&gt;Preact has minor API differences from React (e.g., &lt;code&gt;class&lt;/code&gt; vs &lt;code&gt;className&lt;/code&gt;). Update your ESLint config to accommodate&lt;br&gt;
these differences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  files: ['**/*.ts', '**/*.tsx'],
  // other properties...
  rules: {
+   'react/prop-types': 'off',
+   'react/no-unknown-property': 'off' // Leave this on if you want to use `className` instead.
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating tsdown configuration
&lt;/h3&gt;

&lt;p&gt;Update the external dependencies for Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
- external: ['react', 'react/jsx-runtime'],
+ external: ['preact', 'preact/jsx-runtime', 'preact/compat'],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test setup changes
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;vitest.config.ts&lt;/code&gt; for Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-import react from '@vitejs/plugin-react'
+import preactPlugin from '@preact/preset-vite'
import { defineConfig } from 'vitest/config'

export default defineConfig({
- plugins: [react()],
+ plugins: [preactPlugin()],
  test: {
    environment: 'happy-dom',
    globals: true,
    setupFiles: './tests/setup.ts',
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the testing library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm remove @testing-library/react @testing-library/user-event
pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @testing-library/preact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update test types in &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "types": [
      "node",
      "@testing-library/jest-dom",
      "vitest",
      "vitest/globals"
      // Enables global test functions
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefit of globals:&lt;/strong&gt; You can use &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, and &lt;code&gt;beforeEach&lt;/code&gt; without importing them.&lt;/p&gt;

&lt;p&gt;Update your test files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ./tests/index.test.tsx
-import { render, screen } from '@testing-library/react'
+import { render, screen } from '@testing-library/preact';
import { MyButton } from '../src'

test('button', () =&amp;gt; {
  // test code...
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Playground configuration
&lt;/h3&gt;

&lt;p&gt;Update the playground to use Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// playground/vite.config.ts
-import react from '@vitejs/plugin-react'
+import preactPlugin from '@preact/preset-vite'
import { defineConfig } from 'vite'

export default defineConfig({
  root: './playground',
- plugins: [react()]
+ plugins: [preactPlugin()]
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Verify everything works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;test&lt;/span&gt;         &lt;span class="c"&gt;# Should pass&lt;/span&gt;
pnpm build        &lt;span class="c"&gt;# Should complete without errors&lt;/span&gt;
pnpm playground   &lt;span class="c"&gt;# Should display the button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/dc26deaa7b9321b35e06e485fd0eff7ce4cbcc8c" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;dc26dea&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Preact template:&lt;/strong&gt; &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/preact" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;templates/preact&lt;/code&gt; branch&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuring CSS support
&lt;/h2&gt;

&lt;p&gt;It's nearly time to add TailwindCSS to our component library. But first, let's understand the CSS bundling problem with&lt;br&gt;
tsdown.&lt;/p&gt;
&lt;h3&gt;
  
  
  Demonstrating the problem
&lt;/h3&gt;

&lt;p&gt;Let's first understand why CSS support in tsdown needs additional tooling.&lt;/p&gt;

&lt;p&gt;Create two CSS files to test component-level and global styles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component styles&lt;/strong&gt; (&lt;code&gt;src/MyButton.css&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.my-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&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;&lt;strong&gt;Global styles&lt;/strong&gt; (&lt;code&gt;src/index.css&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.global-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&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;Import the component styles in &lt;code&gt;MyButton.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'

+import './MyButton.css'

interface MyButtonProps {
  type?: 'primary'
}

export const MyButton: React.FC&amp;lt;MyButtonProps&amp;gt; = ({ type }) =&amp;gt; {
- return &amp;lt;button className="my-button"&amp;gt;my button: type {type}&amp;lt;/button&amp;gt;
+ return &amp;lt;button className="my-button global-button"&amp;gt;
+   my button: type {type}
+ &amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import global styles in &lt;code&gt;src/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+import './index.css';

export { MyButton } from './MyButton'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test 1: Source code works
&lt;/h3&gt;

&lt;p&gt;Run the playground (using source code directly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcqtbk3oaklilljznc9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcqtbk3oaklilljznc9d.png" alt="Playground app showing styled button" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Success!&lt;/strong&gt; Both CSS files apply correctly when Vite loads the source code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Built code fails
&lt;/h3&gt;

&lt;p&gt;Now test the built version. Update &lt;code&gt;playground/main.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-import { MyButton } from '../src'
+import { MyButton } from '../dist'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcatdwljwmc7x58eecbqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcatdwljwmc7x58eecbqc.png" alt="Playground app showing unstyled button" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The styles are gone!&lt;/strong&gt; What happened?&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the problem
&lt;/h3&gt;

&lt;p&gt;Inspect &lt;code&gt;dist/index.js&lt;/code&gt;:&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;// Generated by tsdown&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsxs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// No CSS imports&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsxs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-button global-button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Classes are referenced&lt;/span&gt;
    &lt;span class="na"&gt;children&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;my button: type &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The issue:&lt;/strong&gt; While tsdown generates &lt;code&gt;dist/index.css&lt;/code&gt; with your styles, it doesn't inject the import statement into the&lt;br&gt;
JavaScript bundle. Users would need to manually import the CSS:&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="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-library/dist/index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Manual import required&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-library&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;p&gt;This is cumbersome and defeats the purpose of a self-contained component library.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/3ab2a8c0e9091f423ddadae45af33e8d44564bd6" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;3ab2a8c&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Solving CSS injection
&lt;/h2&gt;

&lt;p&gt;To fix this, I created &lt;a href="https://github.com/bosh-code/tsdown-plugin-inject-css" rel="noopener noreferrer"&gt;&lt;code&gt;@bosh-code/tsdown-plugin-inject-css&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
which automatically injects CSS imports into JavaScript files built with tsdown.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing the plugin
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @bosh-code/tsdown-plugin-inject-css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Configuring the plugin
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;tsdown.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'tsdown';
+import { injectCssPlugin } from '@bosh-code/tsdown-plugin-inject-css';

export default defineConfig({
    // other config...
+ plugins: [
+   injectCssPlugin(),
+ ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying the fix
&lt;/h3&gt;

&lt;p&gt;Rebuild and run the playground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcqtbk3oaklilljznc9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcqtbk3oaklilljznc9d.png" alt="Playground app showing styled button" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works!&lt;/strong&gt; Check &lt;code&gt;dist/index.js&lt;/code&gt; to see the injected import:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsxs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react/jsx-runtime&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="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Injected by the plugin&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsxs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-button global-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&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="s1"&gt;my button: type &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you're not using TailwindCSS&lt;/strong&gt;, you can stop here. Your component library now supports both component-level and&lt;br&gt;
global styles with automatic CSS injection. For a guide on how to add CSS Module support, check&lt;br&gt;
out &lt;a href="https://bosher.co.nz/blog/tsdown-react-css-modules" rel="noopener noreferrer"&gt;this article&lt;/a&gt; I wrote.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/commit/a1f1570dba71ff1c39354ae0762b7a96e1915265" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;a1f1570&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding TailwindCSS
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The TailwindCSS plugin
&lt;/h3&gt;

&lt;p&gt;While TailwindCSS has an excellent &lt;a href="https://tailwindcss.com/docs/installation/vite" rel="noopener noreferrer"&gt;&lt;code&gt;@tailwindcss/vite&lt;/code&gt;&lt;/a&gt; plugin, it uses&lt;br&gt;
Vite-specific features that don't work with tsdown. To solve this, I created &lt;a href="https://github.com/bosh-code/tsdown-plugin-tailwindcss" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;@bosh-code/tsdown-plugin-tailwindcss&lt;/code&gt;&lt;/a&gt;, which provides the same&lt;br&gt;
functionality for tsdown.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Install TailwindCSS and the tsdown plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @bosh-code/tsdown-plugin-tailwindcss tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Add the TailwindCSS plugin to your &lt;code&gt;tsdown.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'tsdown';
import { injectCssPlugin } from '@bosh-code/tsdown-plugin-inject-css';
+import { tailwindPlugin } from '@bosh-code/tsdown-plugin-tailwindcss';

export default defineConfig([
  {
    entry: ['./src/index.ts'],
    external: ['react', 'react/jsx-runtime'],
    platform: 'browser',
    dts: true,
    minify: process.env.NODE_ENV === 'prod',
    sourcemap: true,
    plugins: [
      injectCssPlugin(),
+     tailwindPlugin({
+       minify: process.env.NODE_ENV === 'prod'
+     })
    ]
  }
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Plugin order matters:&lt;/strong&gt; Keep &lt;code&gt;injectCssPlugin()&lt;/code&gt; before &lt;code&gt;tailwindPlugin()&lt;/code&gt; to ensure CSS is processed before&lt;br&gt;
injection.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up TailwindCSS
&lt;/h3&gt;

&lt;p&gt;Replace the content of &lt;code&gt;src/index.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+@import "tailwindcss";

-.global-button {
-  color: black;
-}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the component CSS file and update your button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;src/MyButton.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'
-import './MyButton.css'

interface MyButtonProps {
  type?: 'primary'
}

export const MyButton: React.FC&amp;lt;MyButtonProps&amp;gt; = ({ type }) =&amp;gt; {
- return &amp;lt;button className="my-button global-button"&amp;gt;
+ return &amp;lt;button className="text-red-500"&amp;gt;
    my button: type {type}
  &amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, build the library and run the playground again (make sure the plugin is using the &lt;code&gt;dist/&lt;/code&gt; version of your button):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make sure &lt;code&gt;playground/main.tsx&lt;/code&gt; imports from &lt;code&gt;../dist&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;// Testing built version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the TailwindCSS class applied:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdy7b8zz6jt86op4uxg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdy7b8zz6jt86op4uxg8.png" alt="Playground app showing TailwindCSS styled button" width="725" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Success!&lt;/strong&gt; TailwindCSS utilities are working in the built library.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/b81ff29ad65d7c7f37a6c5984c2361a935721ab2" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;b81ff29&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuring the playground for development
&lt;/h2&gt;

&lt;p&gt;There's one more step to setting up TailwindCSS: making it work when the playground loads source code directly (for&lt;br&gt;
development).&lt;/p&gt;
&lt;h3&gt;
  
  
  The issue
&lt;/h3&gt;

&lt;p&gt;Switch back to importing from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-import { MyButton } from '../dist'
+import { MyButton } from '../src'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the playground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice TailwindCSS classes don't apply. &lt;strong&gt;Why?&lt;/strong&gt; The playground uses Vite, which needs its own TailwindCSS&lt;br&gt;
configuration to process utility classes during development.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Vite's TailwindCSS plugin
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @tailwindcss/vite
pnpm approve-builds &lt;span class="c"&gt;# if prompted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Configuring Vite
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;playground/vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
+import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  root: './playground',
  plugins: [
    react(),
+   tailwindcss()
  ],
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring TailwindCSS scanning
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;src/index.css&lt;/code&gt; to tell TailwindCSS where to find utility classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s1"&gt;"./**/*.tsx"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;source(none)&lt;/code&gt; - Disables default scanning&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@source "./**/*.tsx"&lt;/code&gt; - Explicitly scans all &lt;code&gt;.tsx&lt;/code&gt; files for utility classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures TailwindCSS includes only the utilities actually used in your components, keeping bundle sizes small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Run the playground again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkb2ttk59eeo02qteqt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkb2ttk59eeo02qteqt5.png" alt="Playground showing TailwindCSS working in dev mode" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perfect!&lt;/strong&gt; TailwindCSS now works in both development (source) and production (built) modes.&lt;/p&gt;

&lt;p&gt;🚩 &lt;strong&gt;Checkpoint&lt;/strong&gt;: &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/f484801304c50de87d03b1d04b0ed7891480b477" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;code&gt;f484801&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Available templates
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; includes several ready-to-use&lt;br&gt;
templates for different setups:&lt;/p&gt;

&lt;h3&gt;
  
  
  UI Framework Templates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/daisyui" rel="noopener noreferrer"&gt;&lt;code&gt;templates/daisyui&lt;/code&gt;&lt;/a&gt; -
TailwindCSS + daisyUI components&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/shadcn" rel="noopener noreferrer"&gt;&lt;code&gt;templates/shadcn&lt;/code&gt;&lt;/a&gt; -
TailwindCSS + shadcn/ui components&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preact Templates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/preact" rel="noopener noreferrer"&gt;&lt;code&gt;templates/preact&lt;/code&gt;&lt;/a&gt; - Preact with
TailwindCSS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/tree/templates/preact-shadcn" rel="noopener noreferrer"&gt;
&lt;code&gt;templates/preact-shadcn&lt;/code&gt;&lt;/a&gt; -
Preact + shadcn/ui (see README for setup notes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/branches" rel="noopener noreferrer"&gt;View all available templates →&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Getting help
&lt;/h2&gt;

&lt;p&gt;If you encounter issues not covered here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/issues" rel="noopener noreferrer"&gt;GitHub repository issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Review the &lt;a href="https://tsdown.dev/" rel="noopener noreferrer"&gt;tsdown documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Compare your code with the checkpoint commits&lt;/li&gt;
&lt;/ol&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have a complete, production-ready React component library with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TailwindCSS styling with automatic purging&lt;/li&gt;
&lt;li&gt;TypeScript with generated declarations&lt;/li&gt;
&lt;li&gt;Optimized bundling with tsdown&lt;/li&gt;
&lt;li&gt;Development playground for testing&lt;/li&gt;
&lt;li&gt;Automated CSS injection&lt;/li&gt;
&lt;li&gt;Source maps for debugging&lt;/li&gt;
&lt;li&gt;Minification for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feedback and contributions
&lt;/h3&gt;

&lt;p&gt;Found an issue or have a suggestion?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open an issue on the &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template/issues" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Submit a pull request with improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Acknowledgments
&lt;/h3&gt;

&lt;p&gt;Thanks to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://voidzero.dev/" rel="noopener noreferrer"&gt;void(0)&lt;/a&gt; team for creating tsdown&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Gugustinette" rel="noopener noreferrer"&gt;Gugustinette&lt;/a&gt; for the create-tsdown tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/emosheeep" rel="noopener noreferrer"&gt;emosheep&lt;/a&gt;
for &lt;a href="https://github.com/emosheeep/vite-plugin-lib-inject-css" rel="noopener noreferrer"&gt;vite-plugin-lib-inject-css&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Happy building!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I hope this guide helps you get started with tsdown. If you found it useful, please consider starring&lt;br&gt;
the &lt;a href="https://github.com/bosh-code/tsdown-react-tailwind-template" rel="noopener noreferrer"&gt;repo&lt;/a&gt; and sharing it with others.&lt;/p&gt;

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