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:
- React components from a local package
- Raw inline SVG
- Webfont / icon font
- 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-srcdependency 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"></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 |
Recommended CSP patterns
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.