Building Reusable UI Components with Vite Library Mode
Learn how to use Vite’s Library mode to build a reusable UI component that outputs ES, UMD, and CJS bundles, with a concrete button example, configuration, and verification steps.
09 Mar 2026, 23:02 UTC

Problem: Sharing code without dragging a whole app
When you have a UI component that you want to reuse across multiple projects, copying the source files leads to drift and maintenance overhead. Publishing a package to npm solves this, but setting up a build that produces both tree‑shakable ES modules and compatible UMD/CJS bundles can be tedious.
Thesis: Vite’s Library mode gives you a zero‑configuration‑ish way to build a publishable library while keeping the fast developer experience you already enjoy.
What Library mode does
Library mode is activated by the build.lib option in vite.config.js. Vite delegates the actual bundling to Rollup, which emits:
- An ES module file (
*.es.js) that bundlers can tree‑shake. - A UMD bundle (
*.umd.js) for direct script inclusion. - Optionally a CommonJS file (
*.cjs.js) for older Node environments.
During development you still get Vite’s HMR and esbuild‑based dependency pre‑bundling, so editing the library feels as snappy as editing a regular Vite app.
Worked example: a simple button component
- Create the library folder
mkdir vite-button-lib cd vite-button-lib npm init -y npm i -D viteYou need write permission in the directory; no elevated privileges are required.
- Add a component
Create
src/Button.tsx(or.jsif you prefer JavaScript):export function Button(props: { children: React.ReactNode; onClick?: () => void }) { return ( {props.children} ); } - Configure Library mode
Add a
vite.config.jsat the project root:import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], build: { lib: { // entry point of the library entry: path.resolve(__dirname, 'src/Button.tsx'), // the name exposed in the UMD build name: 'Button', // file name patterns for the generated bundles fileName: (format) => `button.${format}.js`, }, // optional: rollup options to externalize react if you want it as a peer dependency rollupOptions: { external: ['react', 'react-dom'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM' } } } }, });If you are using plain JavaScript, replace the
.tsxentry with.jsand drop the React plugin. - Build the library
npx vite buildAfter the command finishes, inspect the
distfolder. You should see files similar to:button.es.jsbutton.umd.jsbutton.cjs.js(if you kept the default)
Open one of them to confirm the export is present (e.g.,
export function Button …). No invented output is shown here; you will see the actual content of your source. - Consume the library locally
From the library directory:
npm linkThen, in a separate consumer project:
npm link vite-button-libNow you can import the button:
import { Button } from 'vite-button-lib'; function App() { return ( alert('clicked')}>Press me ); } export default App;Run the consumer’s dev server (
npm run devorvite) and verify the button renders and responds to clicks. No special configuration is needed beyond linking.
Trade‑offs and limitations
- Dev server features are omitted: Library mode does not automatically serve an HTML page or apply Vite’s CSS preprocessing plugins. If you need to demo the component with hot‑reloading, you must add a separate
index.htmland configure plugins manually. - Peer dependencies must be declared: Libraries that depend on React, Vue, etc., should list those packages in
peerDependenciesinpackage.json. Forgetting to do so causes Rollup to bundle the dependency, leading to version clashes when the consumer already has its own copy. - SSR support is limited: The entry point is assumed to run in a browser. For server‑side rendering you need a separate build target or a wrapper that conditionally exports a server‑only version.
Practical verification steps
- Check the
distfolder for the three expected files (.es.js,.umd.js,.cjs.js). - Run
npm linkin the library andnpm link <library-name>in a consumer, then import and use the component in a browser. - Optionally, add
rollup-plugin-visualizerto therollupOptionsand view the generated report to confirm that tree‑shaking removed any unused code.
These steps let you confirm that the build produced the intended formats without guessing.
Closing
Vite Library mode removes much of the boilerplate traditionally associated with publishing a reusable JavaScript or TypeScript package. By configuring build.lib you get ES modules for modern bundlers, UMD for script tags, and CJS for legacy Node—all while retaining Vite’s fast development loop. Keep the caveats about peer dependencies and SSR in mind, run the verification steps above, and you’ll have a reliable, shareable component ready for consumption.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.