← Blog

CSS in SVG — what's supported in PDF conversion

SVG with CSS: render-time evaluation, then mapped to static PDF state Three ways CSS appears in SVG Inline style: style="fill:red; stroke-width:2" Internal stylesheet: <style> .my-class { fill: red } </style> External stylesheet: <?xml-stylesheet href="theme.css"?> In conversion All CSS resolves to per-element static styles before PDF emission. External stylesheets aren't fetched; they must be inlined. Animations, hover states, and JS-driven changes lose their dynamics.

SVG and CSS were designed to work together: most SVG attributes can be overridden by CSS rules. This means an SVG export from a modern design tool may carry a substantial stylesheet, with CSS selectors pointing to elements with classes. The underlying renderer evaluates the stylesheet, computes effective styles for every element, and produces a PDF with those styles baked in.

What the renderer evaluates

For each element in the SVG, the renderer computes the element's effective styles:

  1. Start with default values per the SVG spec.
  2. Apply individual presentation attributes (fill="red", etc.; lowest author priority, beaten by any CSS rule).
  3. Apply matching CSS rules from any <style> blocks, in source order.
  4. Apply inline style="..." attribute (highest author priority, overrides class rules).
  5. Inherit applicable values from the element's parent.

The result is a static set of values for fill, stroke, opacity, font, etc. for each element. This static state is what becomes PDF graphics state.

Well-supported CSS features

Modern server-side SVG renderers support most of CSS 2.1 plus a substantial subset of CSS 3:

Features that don't translate

Anything that requires runtime behavior is gone in PDF:

For SVGs designed with multiple visual states (interactive infographics, animated logos), the PDF captures only the initial appearance.

External stylesheets — inline before upload

SVG can reference external CSS via <?xml-stylesheet?> or via <link> in HTML-embedded SVG:

<?xml-stylesheet type="text/css" href="theme.css"?>
<svg ...> ... </svg>

The conversion doesn't fetch theme.css over the network. The styles in that file effectively don't exist during conversion; affected elements use default styles.

Fix: inline the CSS into a <style> block inside the SVG before upload:

<svg ...>
  <style>
    /* contents of theme.css go here */
    .button-fill { fill: #1e3a5f; }
    .button-stroke { stroke: #d4691a; stroke-width: 2; }
  </style>
  ...
</svg>

CSS custom properties (variables)

CSS variables (--my-color: red) are part of CSS Custom Properties Level 1 and are typically supported by modern SVG renderers. They resolve at the same time as other styles; the PDF gets the resolved value baked in.

Edge case: variables defined on the root and referenced deep inside the SVG work as expected. Variables that depend on JavaScript-driven changes (e.g. :root.dark { --bg: black } toggled by JS) don't change during conversion.

CSS font properties (font-family, font-size, font-weight, font-style) work standardly. @font-face with data URIs embeds custom fonts into the SVG; the renderer uses them during rendering and the PDF embeds them in its font dictionary.

External @font-face URLs (@import url(https://fonts.googleapis.com/...)) don't fetch — those fonts are unavailable during conversion. Fall back to system fonts.

Transform property

CSS transforms are equivalent to SVG transform attribute:

style="transform: rotate(45deg) scale(0.5)"
attribute transform="rotate(45) scale(0.5)"

Both are supported, both translate to PDF's cm (concatenate matrix) operator. CSS transforms allow some additional features (3D transforms) that PDF doesn't support; those degrade to 2D approximations.

Filter and mask via CSS

CSS filter and mask properties can reference SVG filters and masks:

.shadowed { filter: url(#shadow); }

This is equivalent to the SVG filter="url(#shadow)" attribute. The behavior is the same — CSS filter chains rasterize, masks may rasterize. See dedicated posts on filters and masks for details.

CSS-only filters (filter: blur(2px)) without an SVG filter element are typically handled by the renderer as if an internal feGaussianBlur were declared, and the affected region rasterizes accordingly.

Testing CSS resolution before upload

Drop the SVG into a browser tab and check that all colors and styles match what you expect. If everything renders correctly in the browser but the resulting PDF looks plain or differently styled, the converter's CSS handler probably didn't pick up some advanced rule. The reliable workaround is to flatten the styles before uploading: open the SVG in a desktop editor that exposes the XML, and convert class-based styles to direct attributes on each element.

Some SVG editors and online optimizers offer an "inline styles" or "flatten CSS" pass that converts class-based styles to inline attributes on each element. The result is more verbose but no longer depends on the renderer understanding CSS — useful when transferring SVGs across tools with different CSS handling.