🐛 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 direct stringToObjectStyle unit tests. This still verifies that custom property values (including units) are preserved in the generated output.
Workaround
Note: This quick workaround only handles integer px values (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
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
🐛 Bug Report
When converting inline styles, SVGR strips the
pxunit from CSS custom property values (e.g.,--s: 20pxbecomes--s: 20). React does not auto-appendpxto custom properties, so rendering breaks.To Reproduce
Expected output
Why This Happens
In
packages/hast-util-to-babel-ast/src/stringToObjectStyle.ts, theformatValuefunction converts numeric values (andpx-suffixed values) to numbers for all properties:This optimization is safe for standard CSS properties because React auto-appends
pxto 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: 1from 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
formatValueand preserve string values for CSS custom properties:Update the call site in
stringToObjectStyle:Suggested Test Cases
Workaround
Wrap CSS custom property values in
calc()to bypass the/^\\d+px$/regex:Related
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 --clipboardPaste 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