Bundle size is a first-class concern in 2026, and the best libraries have internalized it. Day.js (2KB) replacing Moment.js (300KB), Zustand (2KB) replacing Redux (7KB+middleware), nanoid (1KB) replacing uuid (14KB) — the pattern is clear: modern JavaScript libraries are getting smaller, not bigger. Here are the best sub-5KB packages by category, and how to check bundle size before installing anything.
- Day.js: 2KB — full date library, Moment.js-compatible API
- Zustand: 2KB — complete state management, no provider needed
- nanoid: 1KB — secure unique ID generation, replaces uuid (14KB)
- clsx: 0.5KB — className utility that shadcn/ui standardized
- Use bundlephobia.com — check size BEFORE installing, includes gzipped + minified
npx bundlephobia-cli package-name
npx bundlephobia-cli moment
npx bundlephobia-cli dayjs
npx bundlephobia-cli date-fns
| Package | Gzipped | Notes |
|---|
| Day.js | 2.7KB | Moment.js-compatible API |
| date-fns (used functions) | ~2-8KB | Tree-shakeable, functional |
| Luxon | 23KB | Full Intl support, larger |
| Moment.js | 72KB | Do not use for new projects |
import dayjs from 'dayjs';
dayjs('2026-03-08').add(7, 'day').format('YYYY-MM-DD')
| Package | Gzipped | Notes |
|---|
| nanoid | 1.1KB | URL-safe, cryptographically secure |
| uuid v4 (just v4) | ~3KB | Tree-shakeable in v9+ |
| cuid2 | 2KB | Monotonic, sortable |
Built-in crypto.randomUUID() | 0KB | Node 18+, Web Crypto API |
import { nanoid } from 'nanoid';
const id = nanoid();
const shortId = nanoid(10);
const id = crypto.randomUUID();
| Package | Gzipped | Notes |
|---|
| clsx | 0.5KB | className builder (shadcn/ui standard) |
| cn (tailwind-merge + clsx) | ~3KB | Tailwind className merging |
| klona | 1.1KB | Deep clone (faster than structuredClone for small objects) |
| mitt | 0.3KB | Tiny event emitter |
| ms | 0.5KB | Time string to ms: ms('2 days') |
| bytes | 0.8KB | Bytes to human: bytes(1024) → '1KB' |
import { clsx } from 'clsx';
clsx('foo', { bar: true, baz: false }, ['qux'])
import { cn } from '@/lib/utils';
cn('px-4 py-2', isLarge && 'px-8 py-4', className)
import mitt from 'mitt';
const emitter = mitt<{ userLogin: string; logout: void }>();
emitter.on('userLogin', (userId) => console.log(userId));
emitter.emit('userLogin', '123');
| Package | Gzipped | Notes |
|---|
| Zustand | 1.8KB | Complete store, no boilerplate |
| Valtio | 2.5KB | Proxy-based, auto-subscription |
| Jotai (core) | 3.1KB | Atomic model, TypeScript-first |
| nanostores | 1.1KB | Framework-agnostic atoms |
| Nano Stores | 1.1KB | Best for multi-framework monorepos |
import { create } from 'zustand';
const useStore = create<{ count: number; increment: () => void }>((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));
| Package | Gzipped | Notes |
|---|
| Valibot (tree-shaken) | ~1-3KB | Modular, pay per rule |
| Zod (tree-shaken) | ~4-6KB | Better DX, more ecosystem support |
| TypeBox | ~3KB | JSON Schema + TypeScript |
| ArkType | ~5KB | TypeScript syntax validation |
import { object, string, email, minLength, parse } from 'valibot';
const schema = object({ email: string([email()]), name: string([minLength(2)]) });
| Package | Gzipped | Notes |
|---|
| Built-in fetch | 0KB | Node 18+, modern browsers |
| ky | 2.5KB | fetch-based, retries, timeout |
| wretch | 3.2KB | fetch wrapper, middleware pattern |
| ofetch | 3.8KB | Used by Nuxt, H3 |
import ky from 'ky';
const data = await ky.get('https://api.example.com/users', {
timeout: 5000,
retry: 3,
}).json();
async function get<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`${res.status} ${url}`);
return res.json();
}
| Package | Gzipped | Notes |
|---|
| react-hot-toast | 3.5KB | Toast notifications |
| sonner | 3.2KB | More polished toasts |
| use-debounce | 0.9KB | Debounce hook |
| react-use | varies | Hook library (import individually) |
import { Toaster, toast } from 'sonner';
toast.success('Saved!');
toast.error('Failed to save');
toast.promise(saveUser(), {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed',
});
npm view package-name dependencies
Compare bundle sizes and package health on PkgPulse.