slidev icon indicating copy to clipboard operation
slidev copied to clipboard

Navigation bar does not show icons when using slidev used in iframe

Open taran13m opened this issue 3 months ago • 1 comments

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:

  1. Create a new Slidev project with a custom theme:

    npm create slidev@latest
    
  2. Create a custom theme in theme/ directory with basic styling (CSS only, no UnoCSS configuration)

  3. Use only native Slidev dependencies in package.json:

    {
      "dependencies": {
        "@slidev/cli": "latest",
        "@slidev/theme-default": "latest"
      }
    }
    
  4. Do not add any custom UnoCSS configuration files (uno.config.ts or slidev.config.ts)

  5. Build the presentation:

    slidev build slides.md --base /slides/presentation/ --out ./output
    
  6. Embed the built output in an iframe in another application:

    <iframe 
      src="/slides/presentation/index.html" 
      style="width: 100%; height: 600px;"
    />
    
  7. Navigate to the page with the iframe

  8. Observe that navigation icons appear invisible/missing

  9. 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-image property set with the icon SVG
  • background-color set to currentColor or appropriate color

Screenshots

Navigation bar appears with invisible icons. The buttons have accessible labels but no visible graphics.

Image

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.ts or slidev.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)"
}

taran13m avatar Oct 06 '25 09:10 taran13m

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.

  1. 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 .png or .svg file but is embedded directly into the CSS using a data:image/svg+xml;... URI.
  2. 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 the iframe).
  3. 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 from data: 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.

  1. Navigate to the parent page (the one containing the iframe).
  2. Open your browser's Developer Tools (F12, or Right-click > Inspect).
  3. Click on the Console tab.
  4. 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.

kermanx avatar Nov 16 '25 05:11 kermanx