greenflag-rn
v0.1.0
Published
Country flags for React Native and Expo — 254 flags in rectangular and circular shapes, with an optional waving animation.
Maintainers
Readme
greenflag-rn
Country flags for React Native and Expo — 254 flags in rectangular and circular shapes, with an optional waving animation.
Flag artwork is Flagpack by Yummygum, used under the MIT licence. See Credits.
import Flag from 'greenflag-rn'
<Flag code="IN" />
<Flag code="IN" shape="circle" size={48} />
<Flag code={user.countryCode} fallback={<Placeholder />} />Contents
- Why this exists
- Install
- Quick start
- Recipes
- API reference
- How it works
- Caveats and gotchas
- Development
- Credits
- Licence
Why this exists
Flagpack publishes an official React package, react-flagpack, but it is
web-only. Its component renders:
<div className="flag size-l"><img src="/flags/l/356.svg" /></div>Three things there cannot work in React Native:
| Web dependency | Why RN can't use it |
|---|---|
| <div> / <img> | No DOM host components |
| className + a CSS file | No CSS; all sizing, borders and shadows live in that stylesheet |
| src="/flags/l/356.svg" | An absolute URL off the web server root. Its CLI copies SVGs into public/, which RN has no equivalent of |
So this isn't a matter of patching react-flagpack — it is structurally
incompatible. greenflag-rn takes the same artwork and renders it through
react-native-svg.
Install
react-native-svg is a peer dependency and must match your Expo SDK, so
install it through Expo rather than npm directly:
npx expo install react-native-svg
npm install greenflag-rnBare React Native (no Expo): install react-native-svg normally, then
cd ios && pod install.
| Requirement | Version |
|---|---|
| react | >= 17 |
| react-native | >= 0.70 |
| react-native-svg | >= 13 |
No Metro configuration is required. A normal install puts a single copy of React and React Native in the tree. (Linking the repo by path is the one case that does need config — see Development.)
Quick start
import Flag from 'greenflag-rn'
function Row() {
return (
<>
<Flag code="IN" /> {/* 32 × 24 */}
<Flag code="IN" shape="circle" /> {/* 24 px circle */}
<Flag code="IN" size={64} hasDropShadow />
<Flag code="IN" wave /> {/* animated */}
</>
)
}Codes are accepted in every ISO 3166 spelling, in any case:
<Flag code="IN" /> <Flag code="IND" /> <Flag code="356" />
<Flag code="in" /> <Flag code={356} /> <Flag code="GB-SCT" />Unknown codes render fallback (default null) instead of throwing, so an
unexpected value from an API never crashes a screen:
<Flag code={apiResponse.country} fallback={<View style={styles.grey} />} />Recipes
A country picker
The common case. code comes from data, so every flag must be available:
import Flag, { searchFlags, flagInfo } from 'greenflag-rn'
function CountryPicker({ query, onPick }) {
return (
<FlatList
data={searchFlags(query)} // matches name, alpha-2/3, numeric
keyExtractor={(code) => code}
renderItem={({ item }) => (
<Pressable onPress={() => onPick(item)}>
<Flag code={item} shape="circle" size={32} />
<Text>{flagInfo(item)!.name}</Text>
</Pressable>
)}
/>
)
}Keeping the bundle small
If you know your flags at build time, import them individually. This path carries no flag registry at all:
import { FlagView } from 'greenflag-rn/view'
import IN from 'greenflag-rn/flags/IN'
import US from 'greenflag-rn/flags/US'
<FlagView xml={IN} shape="circle" size={40} />~4 KB instead of ~900 KB. See Bundle size for why this is a separate entry point rather than tree-shaking.
Waving a flag
<Flag code="IN" wave />
<Flag code="IN" wave wavePeriod={4} /> // slowerGood for a header or a detail screen. Do not enable it for every row of a long list — see Caveats.
API reference
<Flag>
Looks a flag up by code. Imported from greenflag-rn.
| Prop | Type | Default | Notes |
|---|---|---|---|
| code | string \| number | — | alpha-2, alpha-3, numeric, or a GB-SCT-style subdivision. Case-insensitive |
| shape | 'rect' \| 'circle' | 'rect' | |
| size | number \| 'S' \| 'M' \| 'L' | 'L' | Width of the flag's rectangle in both shapes. S/M/L = 16/20/32 |
| hasBorder | boolean | true | Hairline border so pale flags stay visible |
| hasBorderRadius | boolean | true | rect only — circles are always fully rounded |
| hasDropShadow | boolean | false | |
| wave | boolean | false | Loops a ripple. See The waving animation |
| wavePeriod | number | 2.4 | Seconds per ripple cycle |
| borderColor | string | rgba(0,0,0,0.15) | |
| fallback | ReactNode | null | Rendered when code matches nothing |
| style | StyleProp<ViewStyle> | — | Applied to the outer view |
| accessibilityLabel | string | country name | |
<FlagView>
The presentational component, imported from greenflag-rn/view. Same props as
<Flag>, except it takes xml (raw SVG markup) instead of code and fallback.
Helpers
import {
flagInfo, searchFlags, normalizeCode, countryNameFor,
flags, ALPHA2_CODES, COUNTRY_NAMES,
} from 'greenflag-rn'| Function | Returns |
|---|---|
| flagInfo(code) | { alpha2, alpha3, numeric, name } or null |
| searchFlags(query) | Alpha2Code[] — matches name and every code format; an empty query returns all |
| normalizeCode(code) | Canonical alpha-2, or null |
| countryNameFor(code) | Country name, or null |
| flags | Record<Alpha2Code, string> — the raw SVG markup |
| ALPHA2_CODES | All 254 codes, alphabetical |
| COUNTRY_NAMES | Record<Alpha2Code, string> |
flagInfo('ARM') // { alpha2: 'AM', alpha3: 'ARM', numeric: '051', name: 'Armenia' }
normalizeCode(356) // 'IN'
searchFlags('nether') // ['NL']Entry points
| Import | Contains | Cost |
|---|---|---|
| greenflag-rn | Flag, FlagView, helpers, all 254 flags | ~900 KB |
| greenflag-rn/view | FlagView and layout helpers — no flag data | ~2 KB |
| greenflag-rn/flags/XX | One flag's SVG markup | ~1–4 KB each |
How it works
Where the artwork comes from
Flags are generated at build time from
flagpack-core, a
framework-agnostic package that ships the SVG files and ISO code tables. The
generator (scripts/generate.ts):
- Reads the size-
lSVG set. Every flag isviewBox="0 0 32 24", so one vector set scales to any size — there is no need to ship three. - Normalises
mask-typeout of inlinestyleattributes.react-native-svgparses inlinestyleunreliably, and without this, masked flags render as solid colour blocks. - Optimises with SVGO (1988 KB → 910 KB), with
cleanupIdsdisabled. - Asserts that no SVG id appears in two flags, then emits one ES module per flag.
Step 4 is not a formality. react-native-svg resolves url(#id) against a
shared registry, so if two flags both declared <mask id="a">, flags would
render wearing each other's masks. SVGO's cleanupIds would rename Flagpack's
namespaced ids (IN_svg__a) to exactly that. Generation fails loudly if this
ever regresses.
The circular shape
Flags are 4:3 and Flagpack ships no square artwork, so circles are a centre crop: the flag is scaled to cover the circle and its left and right edges are clipped.
size means the same thing in both shapes — the width of the flag's rectangle —
and the circle's diameter is that rectangle's height. Both shapes therefore
occupy the same vertical space, and toggling shape never moves a layout:
<Flag code="IN" size={32} /> // 32 × 24 rectangle
<Flag code="IN" size={32} shape="circle" /> // 24 × 24 circle, same heightThe trade-off: size={32} shape="circle" gives a 24 px circle, not 32 px.
For an exact diameter d, pass size={d * 4 / 3}.
The waving animation
react-native-svg exposes no feTurbulence or feDisplacementMap, so there is
no shader route to a cloth warp without adding Skia. Instead the flag is sliced
into 14 vertical strips, each displaced on a phase-shifted sine so a crest
travels left to right. A soft light/shade gradient slides with it.
Each strip is also sheared to the wave's local slope. Without that, a strip carries one constant offset and every horizontal edge becomes a step — piecewise-constant by construction, so adding strips shrinks the steps but never removes the staircase. Shearing makes it piecewise-linear, so neighbouring strips meet and edges read as curves.
The shear is skewY, not rotate. A vertical displacement field slants
horizontal lines and leaves vertical ones upright — exactly what a shear does.
Rotating tilts both axes, so upright details (Sweden's cross bar, any vertical
edge) swing back and forth and the motion reads as twisting rather than rippling.
skewY is on React Native's native-driver allowlist, so the animation never
touches the JS thread.
A waving rectangle stops clipping, so its outline undulates like cloth rather than being held to a straight edge (the hairline border sits out while it does). A circle keeps its clip and is scaled up just enough that the wave can never uncover an edge. Either way the layout box is unchanged.
Bundle size
greenflag-rn → 3.4 MB bundle
greenflag-rn/view + 2 flags → 2.4 MB bundleMeasured on an Expo SDK 57 iOS Hermes build by grepping the bytecode for per-flag SVG ids — the slim bundle contained only the two flags it imported.
The split is physical, not tree-shaking. Metro does not reliably drop unused
modules, so FlagView lives behind its own entry point with no import path to
the registry. A test asserts that invariant, so it cannot silently regress.
Caveats and gotchas
The default import costs ~1.1 MB of bytecode
import Flag from 'greenflag-rn' bundles all 254 flags, because looking a
flag up from a runtime code requires every flag to be present. That is roughly
1.1 MB of Hermes bytecode.
For a country picker or phone-number input this is exactly what you want. If your
flags are known at build time, use greenflag-rn/view plus individual
greenflag-rn/flags/XX imports instead — about 4 KB.
Waving is not free
Each waving flag draws itself once per strip — 14 draws. Fine for a header, a detail screen, or a handful of flags. Enabling it across a long list means hundreds of simultaneous redraws and will stall the UI thread.
The markup is parsed once per flag and shared across strips, so waving multiplies drawing cost only, not parsing. Waving is disabled automatically when the OS reduce motion setting is on.
A waving rectangle overhangs its box
Because it stops clipping, a rippling rectangle can extend a few pixels past its layout box. The box itself is unchanged, so nothing reflows — but it matters in very tight layouts.
Nepal, and other edge-heavy flags, crop poorly as circles
Nepal is the only non-rectangular national flag; as a circle it loses a lot.
Kuwait and the UAE lose edge detail too. Use shape="rect" for those.
Differences from web Flagpack
- No
gradientprop. The web version's gloss relies on CSSmix-blend-mode: overlay, which React Native has no equivalent for. - The border is flat. Same reason — web Flagpack blends its border.
- No
GB-ENG. Flagpack ships no England artwork. Scotland, Wales, Northern Ireland and the UK subdivisions are all present.
Flags are political
Flag sets encode contested territorial claims. This package ships Flagpack's set unchanged and takes no position on it; if a particular code or its artwork matters for your market, check it before shipping.
Development
npm install # `prepare` generates flags and builds automatically
npm run generate # rebuild flags/ and src/generated/ from flagpack-core
npm run build # compile src/ to dist/
npm test # unit tests + generated-output invariants
npm run gallery # build gallery/index.html, a browsable flag gallery
cd example && npx expo startflags/ and src/generated/ are build artifacts and are not committed. The
prepare script regenerates them on install, which is also what makes a
git-dependency install produce a working package rather than an empty one.
Linking this repo into an app
npm install /path/to/greenflag-rn # npm symlinks a local directoryA symlinked package resolves react and react-native from this repo rather
than the app's, giving the bundle two copies and a startup crash
(TurboModuleRegistry.getEnforcing('PlatformConstants')). That is what
example/metro.config.js exists to fix — copy it if you link this way. A registry
or tarball install does not have the problem.
The example app
example/ is an Expo app that renders all 254 flags in both shapes, with search,
a detail sheet and the waving toggle. It doubles as the visual test: mask and
clip-path breakage only shows up on a real render.
Credits
The flags themselves are not this package's work.
Flagpack — the flag set, designed and maintained by
Yummygum. This package consumes it via
flagpack-core and would not exist
without it. If you use greenflag-rn, please credit Flagpack in your project too.
Flagpack authors:
Flagpack also ships official packages for other platforms — if you are not on React Native, use theirs rather than this one: React, Vue, Svelte and Figma.
Rendering is by
react-native-svg from
Software Mansion.
Licence
MIT — see LICENSE.
Flag artwork is MIT licensed by Yummygum as part of Flagpack; that licence is
reproduced in LICENSE alongside this package's own.
