Phosphor Icons are easy to love: clean set, multiple weights, works fine in React, and the SVG output is usually painless. The annoying part starts when you lock down Content Security Policy and realize your icon strategy has security consequences.

I’ve run into this a lot. Teams pick an icon package early, then add CSP later, and suddenly a harmless-looking icon library turns into a debate about style-src, font-src, inline SVG, third-party CDNs, and whether someone really needs a runtime script to paint a caret.

If you’re using Phosphor Icons, there are four common ways to ship them:

  1. React components from a local package
  2. Raw inline SVG
  3. Webfont / icon font
  4. CDN-hosted script or assets

They all work. They do not all play equally well with CSP.

The short version

If you want the strongest, least annoying CSP:

  • Best default: local React package or local SVG files
  • Usually okay: self-hosted webfont
  • Worst for CSP: third-party CDN script injection

That’s the practical answer. The rest is trade-offs.

Option 1: Phosphor as local React components

This is usually the nicest setup for React, Next.js, Remix, Astro, and similar apps.

Example:

import { House, MagnifyingGlass } from "@phosphor-icons/react";

export function Nav() {
  return (
    <nav>
      <House size={20} weight="duotone" />
      <MagnifyingGlass size={18} />
    </nav>
  );
}

These components generally render SVG in the DOM. From a CSP perspective, that’s great because you’re not loading scripts from a third-party origin just to display icons.

CSP impact

In the common case, this approach does not require special script-src allowances for Phosphor itself, because the icon code is bundled into your app. You also usually don’t need font-src for icons.

A tight baseline policy might look like this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-abc123' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

If your app already has a broader real-world policy, that’s normal too. For example, the live header on headertest.com includes analytics, Cookiebot, and a nonce-based script-src with strict-dynamic:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-MTI4MTcyYjktMjYzNy00YTM3LTg0ZDQtZjU4MGRhY2UyZjE1' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'

That’s a good reminder that icon choices live inside the messiness of a real app, not an empty demo.

Pros

  • Best CSP fit for most frontend apps
  • No third-party icon origin in script-src
  • No font-src dependency for icons
  • Tree-shaking can keep payloads reasonable
  • Easy to theme with props and CSS

Cons

  • Can bloat bundles if imported carelessly
  • Still depends on your framework rendering inline SVG
  • Some SSR setups need attention if icon props differ server/client

My take

If you already use React, this is the boring, solid option. I like boring when CSP is involved.

Option 2: Inline SVG or local SVG files

This is the other strong choice. You either paste the SVG directly into markup or import local .svg assets.

Example inline SVG:

<button class="icon-button" aria-label="Search">
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="20"
    height="20"
    viewBox="0 0 256 256"
    fill="currentColor"
    aria-hidden="true">
    <path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path>
  </svg>
</button>

Or local file usage:

<img src="/icons/search.svg" alt="" />

CSP impact

This depends on how you use SVG.

Inline <svg>...</svg>

Inline SVG markup itself is generally fine under CSP. You’re not executing script. But if you embed event handlers or script inside the SVG, you’ve made a bad choice.

Good:

<svg fill="currentColor"><path d="..." /></svg>

Bad:

<svg onclick="alert(1)">
  <script>alert(1)</script>
</svg>

SVG via <img src="/icons/x.svg">

This falls under img-src, not script-src. That’s usually easy to allow:

img-src 'self' data:;

SVG as CSS background image

That may hit img-src too, but your stylesheet loading still depends on style-src.

Pros

  • Very CSP-friendly when self-hosted
  • No dependency on external font or script origins
  • Crisp rendering at any size
  • Easy to audit

Cons

  • Inline SVG can clutter templates
  • Repeated SVG markup can increase HTML size
  • Sprite systems can add build complexity
  • If your team starts injecting untrusted SVG, things get ugly fast

My take

This is the strongest option if you care about predictable CSP behavior. Also the easiest to reason about during a security review.

Option 3: Phosphor as a webfont

Some teams still prefer icon fonts because they plug into existing CSS conventions.

Example:

@font-face {
  font-family: "Phosphor";
  src: url("/fonts/phosphor.woff2") format("woff2");
}

.icon {
  font-family: "Phosphor";
  speak: none;
}
<i class="icon">&#xe136;</i>

CSP impact

Now icons depend on font-src. If self-hosted:

font-src 'self';
style-src 'self';

If you pull fonts from a CDN, you need to allow that origin in font-src, and often also in style-src if the CSS file is remote.

This is where policies start getting wider than they need to be.

If you want a refresher on directive behavior, csp-guide.com is useful for looking up details like font-src and style-src.

Pros

  • Familiar if your app already uses icon fonts
  • Can be simple in legacy server-rendered apps
  • Easy to size and color with CSS

Cons

  • Worse accessibility and semantics than SVG
  • Requires font-src
  • Harder to inspect and reason about glyph usage
  • Multi-color and weight variants are clunkier
  • FOIT/FOUT-style issues can affect icons too

My take

I’d only use this if I’m stuck in an older stack or inheriting an existing design system. For a new build, SVG wins.

Option 4: CDN-hosted Phosphor script or remote assets

This is the one that tends to cause avoidable CSP pain.

A typical pattern looks like:

<script src="https://unpkg.com/@phosphor-icons/web"></script>

Or loading styles/assets from a third-party domain.

CSP impact

Now you need to allow that external origin in script-src, style-src, font-src, or img-src depending on what the package does.

Example:

script-src 'self' https://unpkg.com;

That’s already a compromise. If the script then dynamically loads more assets, your policy gets more complicated. If you’re trying to maintain a strict nonce-based policy with strict-dynamic, random third-party script includes are usually the wrong direction.

Pros

  • Fast to prototype
  • No package install step
  • Works for demos and throwaway pages

Cons

  • Weakest CSP story
  • Adds third-party trust you probably don’t need
  • Can break when CDN URLs change
  • Harder to pin and audit
  • Can conflict with strict nonce/hash-based policies

My take

Fine for a CodePen. Not my choice for a production app with a serious CSP.

Comparison table

Approach CSP friendliness Main directives involved Good fit
Local React package Excellent script-src for your app bundle only React/Next.js/SPAs
Local inline/self-hosted SVG Excellent img-src if external file, maybe style-src Almost any app
Self-hosted webfont Decent font-src, style-src Legacy systems
Third-party CDN script/assets Poor script-src, style-src, font-src, img-src Prototypes only

Best pattern for React + Phosphor

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

Best pattern for self-hosted SVG files

Content-Security-Policy:
  default-src 'self';
  img-src 'self' data:;
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

If you insist on a webfont

Content-Security-Policy:
  default-src 'self';
  style-src 'self';
  font-src 'self';
  object-src 'none';
  base-uri 'self';

What I’d choose

If I’m building a modern app, I’d pick local React components or self-hosted SVG every time.

  • React app: use @phosphor-icons/react
  • Non-React app: use local SVGs
  • Avoid: third-party script delivery unless there’s a very good reason
  • Only tolerate: icon fonts when I’m working around legacy constraints

That gives you the smallest CSP blast radius and the fewest weird exceptions six months from now when someone asks, “Why do we allow this CDN in script-src again?”

That question always comes eventually. Better to avoid needing an awkward answer.