-
-
Notifications
You must be signed in to change notification settings - Fork 439
SVGR strips px from CSS variables in inline styles #1020
Description
🐛 Bug Report
When converting inline styles, SVGR strips the px unit from CSS custom property values (e.g., --s: 20px becomes --s: 20). React does not auto-append px to custom properties, so rendering breaks.
To Reproduce
- Run (version pinned for reproducibility):
npx @svgr/cli@8.1.0 --no-svgo <<< '<svg style="--s: 20px;" viewBox="0 0 20 20"><path d="M0 0"/></svg>'- Observe output:
import * as React from "react";
const SvgComponent = (props) => (
<svg style={{ "--s": 20 }} viewBox="0 0 20 20" {...props}><path d="M0 0" /></svg>
);
export default SvgComponent;Expected output
import * as React from "react";
const SvgComponent = (props) => (
<svg style={{ "--s": "20px" }} viewBox="0 0 20 20" {...props}><path d="M0 0" /></svg>
);
export default SvgComponent;Why This Happens
In packages/hast-util-to-babel-ast/src/stringToObjectStyle.ts, the formatValue function converts numeric values (and px-suffixed values) to numbers for all properties:
const formatValue = (value: string) => {
if (isNumeric(value)) return t.numericLiteral(Number(value))
if (isConvertiblePixelValue(value))
return t.numericLiteral(Number(trimEnd(value, 'px'))) // ← strips px even for CSS custom properties
return t.stringLiteral(value)
}This optimization is safe for standard CSS properties because React auto-appends px to numeric values (e.g., width: 20 → width: 20px). However, React treats CSS custom property values as raw strings (no unit inference), so the unit is lost.
The fix in #582 (cbdb47f) correctly handled key formatting for --* properties, but its test case used --index: 1 (a unitless number), so this edge case wasn't caught.
Suggested Fix
This makes custom property values always stay as strings. That changes --index: 1 from a number to "1", but that is consistent with how CSS custom properties are interpreted (string values passed through as-is).
Pass the key to formatValue and preserve string values for CSS custom properties:
const formatValue = (value: string, key: string) => {
// CSS custom properties: always preserve original string value
if (VAR_REGEX.test(key)) return t.stringLiteral(value)
if (isNumeric(value)) return t.numericLiteral(Number(value))
if (isConvertiblePixelValue(value))
return t.numericLiteral(Number(trimEnd(value, 'px')))
return t.stringLiteral(value)
}Update the call site in stringToObjectStyle:
const property = t.objectProperty(formatKey(key), formatValue(value, key))Suggested Test Cases
// packages/hast-util-to-babel-ast/src/index.test.ts
it('preserves px unit in CSS custom property values', () => {
expect(stringToObjectStyle('--size: 20px')).toMatchInlineSnapshot(`
{
"--size": "20px",
}
`)
})
it('preserves other units in CSS custom property values', () => {
expect(stringToObjectStyle('--spacing: 1.5rem; --width: 100%')).toMatchInlineSnapshot(`
{
"--spacing": "1.5rem",
"--width": "100%",
}
`)
})
it('preserves complex CSS custom property values', () => {
expect(stringToObjectStyle('--gradient: linear-gradient(red, blue)')).toMatchInlineSnapshot(`
{
"--gradient": "linear-gradient(red, blue)",
}
`)
})Note: In the PR, I covered the regression at the
transform(integration) level rather than adding directstringToObjectStyleunit tests. This still verifies that custom property values (including units) are preserved in the generated output.
Workaround
Note: This quick workaround only handles integer
pxvalues (e.g.,20px). It does not cover decimals, negatives, or other units.
Wrap CSS custom property values in calc() to bypass the /^\\d+px$/ regex:
// svg-css-var-fix-loader.js (use before @svgr/webpack)
module.exports = function(source) {
return source.replace(
/style="([^"]*)"/g,
(match, styleContent) => {
const fixed = styleContent.replace(
/(--[\\w-]+:\\s*)(\\d+px)/g,
'$1calc($2)'
)
return `style="${fixed}"`
}
)
}Related
- Inline css var support #582 — Inline CSS variable support
- cbdb47f — fix: support CSS variables
Link to repl or repo (highly encouraged)
Not available. CLI reproduction included above.
Run npx envinfo --system --binaries --npmPackages @svgr/core,@svgr/cli,@svgr/webpack,@svgr/rollup --markdown --clipboard
Paste the results here:
System:
- OS: Linux 6.12 Amazon Linux 2023
- CPU: (11) arm64 unknown
- Memory: 4.15 GB / 7.65 GB
- Container: Yes
- Shell: 5.2.15 - /bin/bash
Binaries:
- Node: 24.13.0 - /home/node/.nvm/versions/node/v24.13.0/bin/node
- npm: 11.7.0 - /home/node/.nvm/versions/node/v24.13.0/bin/npm
npmPackages:
- @svgr/core: not installed
- @svgr/cli: not installed
- @svgr/webpack: ^8.1.0 => 8.1.0
- @svgr/rollup: not installed