Heroicons are the easy case for CSP, which is nice for once.

If you use Heroicons the normal way — React components, Vue components, Blade components, or copied inline SVG markup — Heroicons do not require their own domain in your Content Security Policy. There’s no Heroicons CDN you need to allow. No remote script. No remote font. Usually no remote image either.

That means your CSP for Heroicons is mostly about how you render the SVGs.

The short answer

Most Heroicons setups work with a CSP like this:

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

If your icons are rendered as inline <svg>, this usually works fine.

Do Heroicons need CSP allowlisting?

Usually, no.

Heroicons are commonly used in a few ways:

  1. Imported as framework components

    • React
    • Vue
  2. Copied as inline SVG

    • pasted directly into HTML templates
  3. Wrapped in your own UI component library

    • still inline SVG under the hood
  4. Saved as local SVG files

    • served from your own origin

Every one of those is compatible with a strict CSP because the browser is not fetching code or assets from Heroicons infrastructure.

What CSP directives matter for Heroicons?

These are the directives I actually care about for icon rendering:

  • img-src
  • style-src
  • script-src
  • default-src

And sometimes:

  • object-src
  • base-uri

If you want the CSP docs for directive behavior, the official reference is here:

For a cleaner CSP-focused breakdown, https://csp-guide.com is also useful.

Case 1: Heroicons as inline SVG

This is the most common setup.

Example:

<button class="icon-button" aria-label="Search">
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="currentColor"
    aria-hidden="true"
    class="size-6"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="m21 21-4.35-4.35m0 0A7.5 7.5 0 1 0 5.5 5.5a7.5 7.5 0 0 0 11.15 11.15Z"
    />
  </svg>
</button>

CSP:

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

Why this works:

  • Inline SVG in the HTML document is not blocked like inline JavaScript.
  • The SVG markup is part of the DOM.
  • Heroicons do not require script-src exceptions.
  • Heroicons do not require font-src exceptions.

Case 2: Heroicons in React

Typical usage:

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'

export function SearchButton() {
  return (
    <button type="button" aria-label="Search">
      <MagnifyingGlassIcon className="h-6 w-6" />
    </button>
  )
}

CSP:

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

Same story: the icon becomes inline SVG in the rendered DOM.

If your React app injects styles at runtime, that’s a framework/tooling CSP issue, not a Heroicons issue. I see people blame the icon package when the real problem is CSS-in-JS needing a nonce or hash.

Case 3: Heroicons in Vue

<script setup>
import { HomeIcon } from '@heroicons/vue/24/solid'
</script>

<template>
  <nav>
    <HomeIcon class="h-5 w-5" aria-hidden="true" />
  </nav>
</template>

CSP:

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

Again, no Heroicons-specific sources needed.

Case 4: Heroicons saved as local SVG files

If you export or store SVG files in your own app and reference them like this:

<img src="/icons/arrow-right.svg" alt="" />

Then your CSP needs to allow your own origin in img-src:

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

That’s already covered by 'self'.

If you host icons on a separate asset domain, add it:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data: https://assets.example.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

What usually breaks is not Heroicons

I’ve debugged a bunch of “CSP broke our icons” reports. Most turned out to be one of these:

1. CSS classes not applying

The SVG renders, but it has no size, wrong color, or weird alignment.

That’s usually style-src, your CSS pipeline, or missing stylesheet loading.

Bad assumption:

style-src 'none';

More realistic:

style-src 'self';

If you rely on inline styles, that’s where the CSP fight starts. Heroicons themselves don’t need inline styles.

2. Data URL icons are blocked

Some apps convert SVGs to data: URLs and use them in <img> or CSS backgrounds.

Example:

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0i..." alt="" />

Then you need:

img-src 'self' data:;

Without data:, those icons fail.

3. SVG loaded through CSS background images

Example:

.icon-search {
  background-image: url('/icons/search.svg');
}

That still falls under asset loading, usually img-src in modern CSP behavior for images. Keep it simple:

img-src 'self' data:;
style-src 'self';

4. You embedded SVG with <object> or <embed>

Don’t do this for icons.

<object data="/icons/search.svg" type="image/svg+xml"></object>

A sane CSP often includes:

object-src 'none';

That will block it, and honestly that’s good. Use inline SVG or <img>.

Copy-paste policies

Minimal CSP for Heroicons

Use this if your app is simple and Heroicons are inline or component-based:

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

Strict CSP for Heroicons with nonce-based scripts

If your app already uses nonces:

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

Heroicons don’t care about the nonce. Your JavaScript does.

Using the real-world header pattern you provided as a base, here’s a cleaned-up version where Heroicons need nothing extra:

Content-Security-Policy:
  default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  script-src 'self' 'nonce-{RANDOM_NONCE}' '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.example.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';

Heroicons still don’t need:

  • https://heroicons.com
  • a CDN source
  • font-src exceptions
  • script exceptions

Express example

app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src 'self'",
      "style-src 'self'",
      "img-src 'self' data:",
      "object-src 'none'",
      "base-uri 'self'",
      "frame-ancestors 'none'",
    ].join('; ')
  )
  next()
})

Nginx example

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

Apache example

Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none';"

Debugging checklist

If Heroicons appear broken under CSP, I check these in order:

  1. Are the icons inline SVG or external files?
  2. Is the browser console showing a real CSP violation?
  3. Is the failure actually CSS-related rather than SVG-related?
  4. Are you using data: URLs for icons?
  5. Did someone use <object> for SVG?
  6. Did your framework inject inline styles or scripts that need separate CSP handling?

That last one gets people all the time.

Common bad fixes

Adding random external domains

I see this a lot:

img-src 'self' https://heroicons.com;
script-src 'self' https://heroicons.com;

That’s cargo-cult CSP. Heroicons don’t need it.

Allowing 'unsafe-inline' for scripts

Also common:

script-src 'self' 'unsafe-inline';

If your only goal is “make icons work,” this is the wrong fix. Heroicons are not blocked because of inline script rules.

Overbroad default-src

default-src * data: blob:;

Sure, the icons will probably work. So will a bunch of things you didn’t mean to allow.

Practical recommendation

If you use Heroicons as React/Vue components or pasted SVG markup, start here:

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

Then expand the policy only for the other stuff in your stack: analytics, consent managers, APIs, CDNs, live reload, whatever.

That’s the whole trick with Heroicons and CSP: keep them local, render them as SVG, and don’t overcomplicate the policy.