FAQ & troubleshooting
Duplicate ids when inlining a flag more than once
Each SVG defines ids for its clip path, gradient and artwork. They are namespaced per flag and per shape (pn-ar-clip for the pennant, pn-ar-circle-clip for the circle), so different flags and different shapes of the same flag never clash. Inserting the same markup twice still yields duplicate ids. Browsers usually render it anyway, until the first copy is hidden with display: none: the other copies then lose their clip path and gradient.
<Pennant>and<PennantSvg>rewrite the ids per instance (pn<scope>-<code>[-<shape>]-, fromuseId), so several copies, shapes or React roots never share ids.<pennant-flag>keeps them inside its shadow root.- For manual
innerHTML, pass a unique prefix:renderFlag(ar, { idPrefix: 'hero' })orloadFlag('ar', { shape: 'circle', idPrefix: 'row-3' }). An<img>(for example withtoDataUri) also isolates the ids.
Content Security Policy
| Format | Needs |
|---|---|
<img>, PNG, CSS classes | img-src for your host or https://cdn.jsdelivr.net |
toDataUri(svg) in <img>/CSS | img-src data: |
| Lazy chunks | script-src for your own origin (they are your bundle) |
| Fetch mode | connect-src for the base host (default https://cdn.jsdelivr.net) |
| Inline SVG, React, Web Component, theming | nothing extra: works with style-src 'self' |
No format needs 'unsafe-inline':
- The flag SVG has no
styleattributes: the bar colors are presentation attributes and theming is a rule inpennants/css/theme.css. - React inline, fetch and
<PennantSvg>output is a<span role="img">around an<svg width height viewBox>, sized with attributes, so it needs no stylesheet either. <pennant-flag>styles its shadow root with a constructable stylesheet (adoptedStyleSheets), which CSP does not restrict. Only browsers without that API fall back to a<style>element.
Two cases still produce a style attribute, which a strict style-src blocks: <Pennant mode="img"> with a CSS-length size (use a number), and any style prop you pass yourself.
SSR and hydration
pennants/reactis safe to import from React Server Components.<PennantSvg>renders on the server;<Pennant>lives in a'use client'module and becomes a client reference.pennants/react/staticexports only<PennantSvg>, for bundlers that would otherwise emit the lazy chunks anyway.<Pennant>(inline mode) renders a correctly sized empty<svg>on the server and loads the flag after hydration. No hydration mismatch, no layout shift, but no flag in the HTML.- For flags in the server HTML, use
mode="img", or<PennantSvg art={ar} />with a static import. fetchPennantand fetch mode run in the browser only: they needDOMParserto sanitize the response, so on the serverfetchPennantrejects. UserenderFlagwith static art,loadFlag, or<PennantSvg art>there.pennants/elementcan be imported on the server:definePennantElementdoes nothing wherecustomElementsdoes not exist. The element renders after it is defined in the browser.getPennantNameusesIntl.DisplayNames. Server and browser can ship different CLDR versions, so a few names may differ between them. Pass an explicittitleif that matters.
ESM only and require
The package is ESM only. Node.js 20.19+ and 22.12+ can require() ES modules without top-level await, which this package does not use:
const { getPennantName } = require('pennants'); // works on Node.js 20.19+On older runtimes use import(). In TypeScript, use moduleResolution: "bundler", "node16" or "nodenext"; the legacy "node" (node10) mode does not read exports and cannot find pennants/react or pennants/art/*.
Bundler asset imports
Importing an asset file returns whatever your bundler is configured to return:
import url from 'pennants/svg/pennant/ar.svg'; // Vite: URL string
import markup from 'pennants/svg/pennant/ar.svg?raw'; // Vite: SVG markup- webpack 5: add a rule with
type: 'asset/resource'for.svgundernode_modules/pennants. - Next.js: a static import of an
.svgfile gives an object; use.src, or the/publicfolder, orpennantUrl. pennants/csswith Vite, SvelteKit or Astro: the simplest fix isimport 'pennants/css/cdn': the same classes, pointing at version-pinned jsDelivr URLs, which bundlers do not touch (2.0 kB gzip of CSS, flags load from the CDN). To self-host the flags instead, keep the default stylesheet and stop the inlining. Background: Vite inlines assets under 4 KiB as data URIs, so most of the 271 flags end up inside your CSS: 376.2 kB min /53.0 kB gzip instead of15.7 kB / 3.8 kB. Keep them as files (in Astro, put the option undervite.build). Next.js andAngular do not inline them.
// vite.config.js
export default {
build: {
assetsInlineLimit: (file) => (file.includes('/pennants/svg/') ? false : undefined),
},
};A flag looks wrong
Flag designs come from flag-icons. Please check the flag-icons repository first; problems with the pennant frame (clipping, bar, sizes) belong in the pennants issue tracker.
A code is missing
isPennantCode and codes list exactly what ships. Requests for new flags go to flag-icons; once a flag is there, it is added here with the next flag-icons update.