Skip to content

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>]-, from useId), 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' }) or loadFlag('ar', { shape: 'circle', idPrefix: 'row-3' }). An <img> (for example with toDataUri) also isolates the ids.

Content Security Policy ​

FormatNeeds
<img>, PNG, CSS classesimg-src for your host or https://cdn.jsdelivr.net
toDataUri(svg) in <img>/CSSimg-src data:
Lazy chunksscript-src for your own origin (they are your bundle)
Fetch modeconnect-src for the base host (default https://cdn.jsdelivr.net)
Inline SVG, React, Web Component, themingnothing extra: works with style-src 'self'

No format needs 'unsafe-inline':

  • The flag SVG has no style attributes: the bar colors are presentation attributes and theming is a rule in pennants/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/react is 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/static exports 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.
  • fetchPennant and fetch mode run in the browser only: they need DOMParser to sanitize the response, so on the server fetchPennant rejects. Use renderFlag with static art, loadFlag, or <PennantSvg art> there.
  • pennants/element can be imported on the server: definePennantElement does nothing where customElements does not exist. The element renders after it is defined in the browser.
  • getPennantName uses Intl.DisplayNames. Server and browser can ship different CLDR versions, so a few names may differ between them. Pass an explicit title if 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:

js
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:

js
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 .svg under node_modules/pennants.
  • Next.js: a static import of an .svg file gives an object; use .src, or the /public folder, or pennantUrl.
  • pennants/css with Vite, SvelteKit or Astro: the simplest fix is import '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 under vite.build). Next.js andAngular do not inline them.
js
// 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.

MIT licensed. Flag artwork from flag-icons (MIT).