How to Ensure Consistent SVG Line Weight with Non-Scaling Stroke in React
Add vectorEffect="non-scaling-stroke" to SVG elements to maintain constant stroke widths regardless of scale transformations.
In the woosal1337/blog repository, SVG icons are rendered directly within TypeScript/React components like IconLink, ViewAllButton, and BackButton. When these components use fixed strokeWidth values combined with Tailwind sizing utilities such as w-8 h-8, the visual line weight scales with the viewBox, creating uneven aesthetics. The SVG specification provides a declarative fix that keeps strokes crisp at any resolution.
Why SVG Strokes Scale Unexpectedly
By default, SVG stroke widths are subject to the coordinate system of the viewBox. When you resize an icon container—scaling the SVG up or down—the stroke grows or shrinks proportionally. In components/ds/icon-link.tsx, the <rect> element at lines 44–46 defines a strokeWidth="1.2" that appears thicker when the parent wrapper expands, breaking visual consistency across the blog's design system.
Solution: Using vector-effect="non-scaling-stroke"
The vector-effect attribute overrides coordinate-system scaling for specific geometric properties. Setting vectorEffect="non-scaling-stroke" instructs the browser to render the stroke in device-pixel units, independent of any transforms or viewBox scaling.
Applying to Individual Elements
Target the specific shape elements in your components. In components/ds/icon-link.tsx, modify the rectangle definition to include the non-scaling attribute:
<rect
x="1.5"
y="3"
width="13"
height="10"
rx="2"
stroke="currentColor"
strokeWidth="1.2"
vectorEffect="non-scaling-stroke"
/>
Similarly, update components/ds/view-all-button.tsx (lines 16–19) and components/ds/back-button.tsx (lines 16–19) to ensure their respective arrow and link icons maintain consistent line weight at all sizes.
Creating a Reusable Wrapper Component
Centralize the fix by creating a SvgIcon wrapper that applies the attribute to the root <svg> element. This ensures every icon in the blog benefits automatically without repetitive markup:
type SvgProps = React.SVGProps<SVGSVGElement>;
export const SvgIcon = ({ children, ...props }: SvgProps) => (
<svg vectorEffect="non-scaling-stroke" {...props}>
{children}
</svg>
);
Consume the wrapper in existing components to guarantee uniform stroke behavior:
import { SvgIcon } from '@/components/ds/svg-icon';
export function MailIcon() {
return (
<SvgIcon viewBox="0 0 16 16" className="w-8 h-8 text-ink-soft">
<rect x="1.5" y="3" width="13" height="10" rx="2"
stroke="currentColor" strokeWidth="1.2" />
<path d="m2.5 4.5 5.5 4 5.5-4"
stroke="currentColor" strokeWidth="1.2"
strokeLinecap="round" />
</SvgIcon>
);
}
Implementation Steps
-
Identify SVG components – Locate files relying on fixed
strokeWidthvalues, specificallycomponents/ds/icon-link.tsx,components/ds/view-all-button.tsx, andcomponents/ds/back-button.tsx. -
Add the vector effect attribute – Insert
vectorEffect="non-scaling-stroke"to shape elements (e.g.,<rect>,<path>,<circle>) or the root<svg>tag. -
Preserve explicit strokeWidth values – Keep your numeric
strokeWidthdeclarations; they now represent pixel-exact thickness rather than relative units. -
Centralize with a wrapper – Create a shared
SvgIconcomponent incomponents/ds/svg-icon.tsxto apply the attribute globally and reduce duplication. -
Run quality checks – Execute
bun run checkandbun run lintto verify the added props comply with Biome linting rules used throughout the project.
Summary
- Non-scaling strokes prevent line weight from growing when SVGs are resized via CSS or container queries.
- Apply
vectorEffect="non-scaling-stroke"directly to shape elements incomponents/ds/icon-link.tsx,view-all-button.tsx, andback-button.tsx, or use a shared wrapper for global consistency. - The
strokeWidthattribute remains required and defines the exact pixel thickness rendered on screen. - Always run
bun run lintafter modifying JSX attributes to maintain codebase standards.
Frequently Asked Questions
What does vector-effect="non-scaling-stroke" do exactly?
It instructs the rendering engine to calculate stroke width in screen-coordinate space rather than user-coordinate space. This means transformations applied to the SVG—whether through CSS scale, viewBox scaling, or matrix transforms—do not affect the thickness of the lines, ensuring consistent SVG line weight across all display contexts.
Should I apply non-scaling-stroke to every SVG element or just the root?
Apply it to the specific elements that draw strokes, such as <path>, <rect>, or <circle>, or apply it once to the root <svg> element to affect all children. In the woosal1337/blog codebase, applying it to individual shape elements within IconLink and BackButton provides granular control, while a wrapper component applies it globally.
Does non-scaling-stroke affect performance?
No, vector-effect="non-scaling-stroke" has negligible performance impact. The browser computes the stroke geometry using a different coordinate system, but this calculation is highly optimized in modern rendering engines and does not introduce perceptible overhead in React applications.
Can I use non-scaling-stroke with CSS instead of attributes?
While the CSS vector-effect property exists in some specifications, browser support is inconsistent compared to the presentation attribute. For maximum compatibility in the woosal1337/blog TypeScript/React environment, use the JSX prop vectorEffect="non-scaling-stroke" directly on the SVG elements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →