A modern, batteries‑included React 18 component library template for TypeScript with ESM‑only bundling, Vite playground, Vitest, ESLint/Prettier, TypeDoc, release‑it, STAN, and optional cloud backup.
👇 NPM & Node Current badges will be activated when you publish your component to NPM!
This template gives you a production‑ready foundation to build and publish React components with TypeScript. It prioritizes a smooth developer experience (DX) with fast feedback, predictable builds, and clear test and lint ergonomics.
What's in the box:
Delightful defaults with modern tooling — batteries included, no drama.
react
, react-dom
, and react/jsx-runtime
(smaller bundles; consumers bring their own React)dist/index.d.ts
for consumers and API docs/playground
for instant feedback without publishingendOfLine: "lf"
for cross‑platform harmony.github/workflows/sync.yml
which calls a shared workflow: https://github.com/karmaniverous/gha-workflows/blob/main/.github/workflows/cloud-sync.ymlRCLONE_CONFIG
repo secret is set (providing auth + destination)..github/workflows/sync.yml
.RCLONE_CONFIG
.sideEffects: false
(tree‑shaking hint): https://webpack.js.org/guides/tree-shaking/Option A — GitHub “Use this template”
Option B — degit (no git history)
npx degit karmaniverous/react-component-npm-package-template-ts my-lib
cd my-lib
git init && git add -A && git commit -m "chore: scaffold from template"
Option C — shallow clone then reset history
git clone --depth=1 https://github.com/karmaniverous/react-component-npm-package-template-ts my-lib
cd my-lib
rm -rf .git
git init && git add -A && git commit -m "chore: scaffold from template"
Replace placeholders (package.json and docs)
Quick package.json edits with npm
npm pkg set name='@your-scope/my-lib' version='0.0.0' \
description='My awesome React component library' \
author='Your Name <you@example.com>' license='MIT' \
repository.type='git' \
repository.url='git+https://github.com/your-org/my-lib.git' \
bugs.url='https://github.com/your-org/my-lib/issues' \
homepage='https://github.com/your-org/my-lib#readme'
Prepare for your first release
npm install
npx lefthook install # optional
npm run lint
npm run test
npm run typecheck
npm run build
Configure your GitHub token (for release-it)
Push to your repo (main branch)
git remote add origin git@github.com:your-org/my-lib.git
git push -u origin main
npm run release
release-it will bump the version (starting from 0.0.0), run lint/test/knip/build, tag, publish to npm, and create a GitHub Release.
Prerequisites
Install and initialize
npm install
npx lefthook install # optional Git hooks (branch naming, etc.)
Run tests and type checks to verify your environment
npm run test
npm run typecheck
npm run build
npm run docs
Start the browser playground (HMR)
npm run dev
# opens http://localhost:5173 (by default)
Structure
Example: a simple HelloWorld component (already included)
// src/components/HelloWorld.tsx
export type HelloWorldProps = { who?: string };
export function HelloWorld({ who = 'World' }: HelloWorldProps): JSX.Element {
return <div>Hello {who}</div>;
}
Re-export in your library entry
// src/index.ts
export { HelloWorld, type HelloWorldProps } from './components/HelloWorld';
Consume from another app after publishing
import { HelloWorld } from '@your-scope/react-component-template';
export default function App() {
return <HelloWorld who="React" />;
}
This template uses:
Add tests alongside components (co‑located)
// src/components/HelloWorld.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { HelloWorld } from './HelloWorld';
describe('HelloWorld', () => {
it('renders default greeting', () => {
render(<HelloWorld />);
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
it('renders custom greeting', () => {
render(<HelloWorld who="Developer" />);
expect(screen.getByText('Hello Developer')).toBeInTheDocument();
});
});
Run tests (with coverage)
npm run test
Notes
The test environment is happy‑dom for speed and stability. You can switch to jsdom later if needed.
Vitest excludes stale compiled tests from .rollup.cache.
Also verify your library entry (src/index.ts) re-exports what you intend. A tiny test can import from the library entry instead of deep paths:
// src/index.test.tsx
import { render, screen } from '@testing-library/react';
import { HelloWorld } from './index';
it('re-exports HelloWorld', () => {
render(<HelloWorld who="Library" />);
expect(screen.getByText('Hello Library')).toBeInTheDocument();
});
A minimal playground is included under playground/ for fast, local viewing with HMR. It imports your components directly from src (no publishing required).
Commands
npm run dev # start Vite dev server for the playground
npm run preview # preview a production build of the playground
Where to edit
Publishing note
Build the library
npm run build
Outputs
Externalized
ESM only
Release flow (optional)
This template uses a TypeScript ESLint flat config in eslint.config.ts with:
Run lint
npm run lint
npm run lint:fix
Node 20.6+ note
{
"lint": "node --import tsx ./node_modules/eslint/bin/eslint.js .",
"lint:fix": "node --import tsx ./node_modules/eslint/bin/eslint.js --fix ."
}
(Your current scripts may already work depending on Node version.)
TypeScript runs in check‑only mode (no emit):
npm run typecheck
Scoping
Generate docs from your TSDoc comments
npm run docs
Output is written to docs/. You can host this via GitHub Pages or your preferred static host. Additional behavior is configured in typedoc.json.
Built for you with ❤️ on Bali! Find more great tools & templates on my GitHub Profile.