A lightweight, performant React library for displaying country flags. Built with TypeScript, optimized for bundle size, and designed to scale from simple projects to complex applications.
- 257 flags sourced from flag-icons — comprehensive global coverage
- Zero runtime overhead — each flag is a plain SVG React component, nothing more
- True code splitting — import only what you use, leave the rest out of your bundles
- Two flexible APIs — direct named import for known flags, lazy component for dynamic ones
- Universal support — ESM + CJS formats work with every modern bundler and Node.js environment
Pick your favorite package manager:
npm i @rdnr/react-country-flags
# or
pnpm add @rdnr/react-country-flags
# or
yarn add @rdnr/react-country-flagsPeer dependency: React 17 or later. Make sure you have React already installed in your project.
When you know which flags you need ahead of time, import them directly. Your bundler will automatically tree-shake everything else, so you only pay for what you use.
import { CL, US, DE } from '@rdnr/react-country-flags';
export default function App() {
return (
<div>
<CL width={32} />
<US width={32} />
<DE width={32} />
</div>
);
}Each flag component accepts all standard SVG attributes (width, height, className, style, etc.) via SVGProps<SVGSVGElement>, so styling and customization work exactly as you'd expect.
If you prefer being explicit about the import path:
import CL from '@rdnr/react-country-flags/flags/CL';When the flag to display is decided at runtime (e.g., user selection, API response), use the <Flag> component. It loads each flag on demand with React.lazy, so nothing is fetched until the component actually renders.
import { Suspense } from 'react';
import Flag from '@rdnr/react-country-flags/Flag';
export default function CountryBadge({ code }: { code: string }) {
return (
<Suspense fallback={<span>…</span>}>
<Flag country={code} width={32} />
</Suspense>
);
}<Flag> has built-in <Suspense> support, so you can pass a fallback directly if you prefer:
<Flag country="BR" width={32} fallback={<span>…</span>} />Invalid country codes are handled gracefully — the component renders nothing without errors or console output, keeping your app stable.
import { CL } from '@rdnr/react-country-flags';
// or
import CL from '@rdnr/react-country-flags/flags/CL';| Prop | Type | Description |
|---|---|---|
width |
number | string |
SVG width |
height |
number | string |
SVG height |
className |
string |
CSS class |
style |
CSSProperties |
Inline styles |
...rest |
SVGProps<SVGSVGElement> |
Any standard SVG attribute |
All flags render with a viewBox so they scale correctly from any width/height.
import Flag from '@rdnr/react-country-flags/Flag';| Prop | Type | Default | Description |
|---|---|---|---|
country |
CountryCode |
— | ISO 3166-1 alpha-2 code (uppercase) |
fallback |
ReactNode |
null |
Shown while the flag module loads |
...rest |
SVGProps<SVGSVGElement> |
— | Forwarded to the underlying SVG |
The union of all 257 supported codes, generated automatically from the flag set.
import type { CountryCode } from '@rdnr/react-country-flags';
const code: CountryCode = 'CL'; // fully autocompletedAll ISO 3166-1 alpha-2 codes from the flag-icons v7 collection, plus special regions like EU, UN, XK (Kosovo), XX (unknown), and several dependent territories.
For the complete list, check src/types.ts.
Each flag exists as an independent ESM module. When you import CL, only the Chile flag is added to your bundle — no central registry, no side effects, no bloat.
| Import | Size (gzipped) | Notes |
|---|---|---|
Single simple flag (e.g. FR) |
~300 B | Minimal SVG content |
Single complex flag (e.g. GB) |
~1–4 KB | Detailed SVG geometry |
<Flag> component |
~1 KB | Flag data loaded only when needed |
The package.json includes "sideEffects": false, which tells webpack, Rollup, and esbuild that they can safely tree-shake unused flags.
- Node.js 18+
- npm 9+
git clone <repo>
cd @rdnr/react-country-flags
pnpm installWant to pick up the latest version of flag-icons? Just run:
pnpm run generateThis script (scripts/generate-flags.ts) handles the entire pipeline:
- Reads SVG files from
node_modules/flag-icons/flags/4x3/(only 2-letter country codes) - Strips vendor-prefixed CSS that would cause TypeScript issues
- Converts each SVG into a TypeScript React component using SVGR + svgo
- Writes the result to
src/flags/XX.tsx - Regenerates
src/types.tswith the updatedCountryCodeunion and updatessrc/index.tswith fresh named exports
pnpm run buildThis outputs to dist/ using tsup:
dist/index.{js,cjs}— all named exportsdist/Flag.{js,cjs}— the lazy Flag componentdist/flags/XX.{js,cjs}— individual flag modules*.d.ts/*.d.cts— complete TypeScript declarations for all exports
If you just want to verify types without running a full build:
pnpm run typecheck{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./Flag": {
"import": "./dist/Flag.js",
"require": "./dist/Flag.cjs"
},
"./flags/*": {
"import": "./dist/flags/*.js",
"require": "./dist/flags/*.cjs"
}
},
"sideEffects": false
}import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/Flag.tsx', 'src/flags/*.tsx'],
format: ['esm', 'cjs'],
splitting: true,
treeshake: true,
dts: true,
clean: true,
external: ['react'],
});splitting: true ensures each flag is emitted as a standalone chunk — no flag data leaks into another flag's output.
import { CL } from '@rdnr/react-country-flags';
// → dist/index.js re-exports from dist/flags/CL.js
// → only dist/flags/CL.js (+ its SVG chunk) makes it into your bundle// dist/Flag.js uses:
lazy(() => import('./flags/CL.js'))
// → dist/flags/CL.js is fetched only when the Flag component rendersEach dist/flags/XX.js is roughly 160 bytes — it re-exports from a content-hashed SVG chunk. The chunking is managed automatically by esbuild's code splitter, ensuring minimal overhead and maximum cache efficiency.
MIT