Skip to content

React

Build a production-ready React component library with Bunup in minutes. Zero config. Just works.

Quick Start

Scaffold a minimal starter or publish-ready React component library in seconds:

sh
bunx @bunup/cli@latest create

Select React Component Library from the options. Now you're ready to build components.

Creating Components

Create your first component:

src/components/button.tsx
tsx
export function Button(props: React.ComponentProps<'button'>): React.ReactNode {
  return <button type="button" {...props} />
}

Export it from your entry point:

src/index.tsx
tsx
export { Button } from './components/button'

Build it:

bash
bunx bunup

Your component is now compiled in dist/index.js with TypeScript declarations in dist/index.d.ts.

Styling Options

Bunup supports multiple styling approaches out of the box. Choose what works best for your library.

Pure CSS

Import CSS directly in your components. Bunup bundles everything automatically.

src/styles.css
css
[data-slot="button"] {
  background: hsl(211, 100%, 50%);
  color: white;
  padding: 0.6rem 1.2rem;
  border: none;
  border-radius: 0.5rem;
  cursor: pointer;
}

[data-slot="button"]:hover {
  background: hsl(211, 100%, 45%);
}
src/components/button.tsx
tsx
export function Button(props: React.ComponentProps<'button'>): React.ReactNode {
  return <button type="button" data-slot="button" {...props} />
}
src/index.tsx
tsx
import './styles.css'

export { Button } from './components/button'

Your CSS is automatically bundled into dist/index.css with cross-browser compatibility. Learn more about CSS support.

CSS Modules

Get automatic class name scoping with CSS modules. Just use .module.css:

src/components/button.module.css
css
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  color: white;
}

.primary {
  background-color: #007bff;
}

.primary:hover {
  background-color: #0056b3;
}
src/components/button.tsx
tsx
import styles from './button.module.css'

export function Button(props: React.ComponentProps<'button'>): React.ReactNode {
  return (
    <button
      type="button"
      className={`${styles.button} ${styles.primary}`}
      {...props}
    />
  )
}

TypeScript definitions are generated automatically - you get full autocomplete and type safety. Learn more about CSS modules.

Tailwind CSS

Use Tailwind CSS v4 with zero PostCSS configuration. Your components work everywhere - consumers don't need Tailwind installed.

Install the Tailwind CSS plugin:

bash
bun add --dev @bunup/plugin-tailwindcss

Add it to your config:

bunup.config.ts
ts
import { defineConfig } from 'bunup'
import { tailwindcss } from '@bunup/plugin-tailwindcss'

export default defineConfig({
  plugins: [tailwindcss()],
})

Create your styles with a scoped prefix to prevent conflicts:

src/styles.css
css
@import "tailwindcss" prefix(mylib);

Use prefixed classes in your components:

src/components/button.tsx
tsx
export function Button(props: React.ComponentProps<'button'>): React.ReactNode {
  return (
    <button
      type="button"
      className="mylib:bg-blue-500 mylib:hover:bg-blue-600 mylib:text-white mylib:px-4 mylib:py-2 mylib:rounded-md"
      {...props}
    />
  )
}
src/index.tsx
tsx
import './styles.css'

export { Button } from './components/button'

The plugin outputs scoped, tree-shaken CSS. Only the classes you use are included, and the prefix prevents conflicts with consumer applications. Learn more about the Tailwind CSS plugin.

Distribution

Configure your package.json for npm publishing:

package.json
json
{
  "name": "my-component-library",
  "version": "1.0.0",
  "type": "module",
  "files": [
    "dist"
  ],
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    },
    "./styles.css": "./dist/index.css",
    "./package.json": "./package.json"
  },
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0",
    "react-dom": "^18.0.0 || ^19.0.0"
  }
}

Consumers import your library like this:

tsx
import 'my-component-library/styles.css'
import { Button } from 'my-component-library'

function App() {
  return <Button>Click me</Button>
}

Inject Styles Optional

Want to skip the separate CSS import? Use the inject styles option to bundle CSS directly into JavaScript:

sh
bunup --css.inject
ts
import { defineConfig } from 'bunup'

export default defineConfig({
  css: {
    inject: true,
  },
})

Or with the Tailwind CSS plugin:

bunup.config.ts
ts
import { defineConfig } from 'bunup'
import { tailwindcss } from '@bunup/plugin-tailwindcss'

export default defineConfig({
  plugins: [
    tailwindcss({
      inject: true,
    })
  ],
})

Now consumers only need to import your components:

tsx
import { Button } from 'my-component-library'

function App() {
  return <Button>Click me</Button>
}

Styles are automatically injected at runtime.

React Compiler

Optimize your React components automatically with the React Compiler plugin. It intelligently memoizes components and hooks to minimize unnecessary re-renders without manual useMemo, useCallback, or memo usage. Learn more about the React Compiler.

Install the React Compiler plugin:

bash
bun add --dev @bunup/plugin-react-compiler

Add it to your config:

bunup.config.ts
ts
import { defineConfig } from 'bunup'
import { reactCompiler } from '@bunup/plugin-react-compiler'

export default defineConfig({
  plugins: [reactCompiler()],
})

That's it! Your components are now automatically optimized during the build. Write React code naturally:

src/components/counter.tsx
tsx
function ExpensiveComponent({ data, onClick }) {
  const processedData = expensiveProcessing(data);

  const handleClick = (item) => {
    onClick(item.id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
}

The React Compiler plugin automatically transforms your code to be more performant without changing its behavior.

Build performance

Since the React Compiler uses Babel for transformations, builds will be slightly slower compared to Bunup's normally instant builds. The impact is small but may be more noticeable in larger applications. This trade-off is expected and worth it for the runtime performance improvements your components will gain.

Configuration Options

Customize which files to process or pass options to the React Compiler:

bunup.config.ts
ts
import { defineConfig } from 'bunup'
import { reactCompiler } from '@bunup/plugin-react-compiler'

export default defineConfig({
  plugins: [
    reactCompiler({
      // Only process .tsx files (default: /\.[jt]sx$/)
      filter: /\.tsx$/,

      // React Compiler configuration
      reactCompilerConfig: {
        target: '18',
      },
    }),
  ],
})

Examples

Check out complete examples in the examples directory:

Released under the MIT License.