Navigation bar does not show icons when using slidev used in iframe
Description
Navigation bar icons in Slidev presentations do not render when the built presentation is embedded in an iframe, even when using Slidev's default/native configuration without any custom UnoCSS setup.
The icon elements are present in the DOM with correct class names (e.g., i-carbon:maximize, i-carbon:arrow-left, i-carbon:arrow-right, etc.) but have computed styles showing:
-
width: 0px -
height: 0px -
display: block -
mask-image: none(no mask image applied) -
background-color: rgba(0, 0, 0, 0)(transparent)
This indicates that UnoCSS's icon preset is not being applied during the build process, even though Slidev should handle this automatically.
Minimal reproduction
Steps to reproduce the behavior:
-
Create a new Slidev project with a custom theme:
npm create slidev@latest -
Create a custom theme in
theme/directory with basic styling (CSS only, no UnoCSS configuration) -
Use only native Slidev dependencies in
package.json:{ "dependencies": { "@slidev/cli": "latest", "@slidev/theme-default": "latest" } } -
Do not add any custom UnoCSS configuration files (
uno.config.tsorslidev.config.ts) -
Build the presentation:
slidev build slides.md --base /slides/presentation/ --out ./output -
Embed the built output in an iframe in another application:
<iframe src="/slides/presentation/index.html" style="width: 100%; height: 600px;" /> -
Navigate to the page with the iframe
-
Observe that navigation icons appear invisible/missing
-
Inspect the icon elements in browser DevTools:
// Inside iframe const icon = document.querySelector('.i-carbon\\:maximize'); const styles = window.getComputedStyle(icon); console.log({ width: styles.width, // "0px" height: styles.height, // "0px" maskImage: styles.maskImage, // "none" backgroundColor: styles.backgroundColor // "rgba(0, 0, 0, 0)" });
Expected behavior
The navigation icons should render properly with:
- Proper width/height (e.g.,
1.2em) -
mask-imageproperty set with the icon SVG -
background-colorset tocurrentColoror appropriate color
Screenshots
Navigation bar appears with invisible icons. The buttons have accessible labels but no visible graphics.
Environment
- Slidev version: 52.2.4
- Browser: Chrome/Safari (latest)
- OS: macOS 24.6.0
- Node version: (latest LTS)
- Installation method: Local project (
npm install @slidev/cli) - Build command:
slidev build slides.md --base /slides/presentation/ --out ./output - Theme: Custom theme (minimal CSS only, no custom UnoCSS config)
-
Important: Using Slidev's native/default UnoCSS configuration (no custom
uno.config.tsorslidev.config.ts)
Additional context
- Icons work correctly in development mode (
slidev slides.md) - Issue only occurs in production build when embedded in iframe
- The icon class names are correctly applied (e.g.,
i-carbon:maximize) - The UnoCSS icon preset styles are completely missing from the built output
- This suggests Slidev's build process is not including the necessary UnoCSS icon preset CSS when building for production
- No custom UnoCSS configuration was added - relying entirely on Slidev's built-in defaults
Debugging evidence
Computed styles from browser DevTools showing icons have no dimensions or mask images applied, despite having correct class names:
{
className: "i-carbon:maximize",
width: "0px",
height: "0px",
display: "block",
maskImage: "none",
backgroundColor: "rgba(0, 0, 0, 0)"
}
I asked Gemini, it seems like a CSP problem
Here is a clear guide on how to diagnose and fix the Content Security Policy (CSP) issue.The Root Cause: CSP vs. data: URIs
The problem you are seeing is a classic security feature, not a build bug.
-
How Slidev/UnoCSS Icons Work: UnoCSS's icon preset (which Slidev uses) renders icons by default using CSS
mask-image. The image itself is not a.pngor.svgfile but is embedded directly into the CSS using adata:image/svg+xml;...URI. -
How CSP Works: When you embed your Slidev presentation in an
iframe, it inherits the Content Security Policy (CSP) from the parent page (the application hosting theiframe). -
The Conflict: A common, secure-by-default CSP will set a policy like
img-src 'self', which only allows images to be loaded from the same domain. This policy blocks images fromdata:URIs.
When the browser tries to render the icon in the iframe, it sees mask-image: url("data:..."), checks the inherited CSP, finds that data: is not an allowed source, and blocks it. This results in mask-image: none and an invisible 0x0 pixel icon, exactly as you observed.
How to Diagnose the Issue
You can confirm this is a CSP issue in under 30 seconds by checking the browser's developer console.
- Navigate to the parent page (the one containing the
iframe). - Open your browser's Developer Tools (F12, or Right-click > Inspect).
- Click on the Console tab.
- Reload the page.
You will almost certainly see an error message in the console that looks like this:
Refused to load image 'data:image/svg+xml;base64,PHN2ZyB4b...' because it violates the following Content Security Policy directive: "img-src 'self'".
This error is the definitive proof that the parent page's CSP is the culprit.
How to Fix the Issue
The fix must be applied to the server configuration of the parent application, not to your Slidev project. You need to modify the Content-Security-Policy HTTP header that the parent application's server sends.
You (or the administrator of the parent application) must add 'data:' to the img-src directive.
Example:
Find the configuration on your server (e.g., in your Nginx/Apache config, helmet.js middleware, or server-side code) that sets the Content-Security-Policy header.
Before (Example of a blocking policy):
Content-Security-Policy: default-src 'self'; img-src 'self' https://some-cdn.com;
After (Example of the fix):
Content-Security-Policy: default-src 'self'; img-src 'self' https://some-cdn.com 'data:';
By adding 'data:' to img-src, you are explicitly telling the browser that it is safe to load images from data: URIs.
Once this change is deployed on the parent application's server, clear your browser cache and reload the page. The icons in the embedded Slidev iframe will now render correctly.