Solving SVG <rect> Rendering Quirks: Radii, Strokes, and Clipping
Stop fighting with blurred SVG edges and asymmetric corners. Learn how <rect> handles radius clamping, stroke alignment, and the coordinate shifts needed for pixel-perfect rendering.
02 Oct 2026, 05:40 UTC

The Problem: The 'Almost' Perfect Rectangle
SVG rectangles seem straightforward, but when you move from a basic box to a production-ready UI component, you hit three common walls: blurred edges on 1px borders, asymmetric corner rounding when using percentages, and disappearing elements when using clipPath. These aren't bugs, but specific behaviors of the SVG coordinate system and the <rect> specification.
The takeaway is that <rect> attributes like rx and ry are not simple offsets; they are computed values that clamp based on the element's dimensions and resolve differently depending on the unit used.
Handling Corner Radius and Clamping
The rx (horizontal radius) and ry (vertical radius) attributes define the curvature of the corners. If you provide only one, the browser copies that value to the other. However, this behavior changes when using percentages.
Percentages resolve against the rectangle's own dimensions: rx resolves against width and ry against height. This leads to a critical behavior called clamping. If a radius exceeds half of the corresponding dimension, the browser clamps it to exactly 50% of that dimension.
For example, if you have a rectangle that is 100px wide and 40px high and set both rx and ry to 30%:
rxbecomes 30px (which is less than half of 100, so it stays 30px).rybecomes 12px (which is less than half of 40, so it stays 12px).
Despite using the same percentage, you get an asymmetric curve because the resolution is relative to the axis, not a global value.
Achieving Pixel-Perfect Edges
By default, SVG strokes are centered on the geometric edge of the shape. If you define a <rect> at x="10" with a stroke-width="1", the stroke actually spans from 9.5px to 10.5px. On a standard display, this results in a blurred, anti-aliased line because it straddles two physical pixels.
To fix this, you have two primary options:
- Coordinate Offsetting: Shift your coordinates by half the stroke width (e.g.,
x="10.5") to align the stroke center with the pixel grid. - Shape Rendering: Use the
shape-rendering="crispEdges"attribute. This disables anti-aliasing and snaps the edges to the device pixel grid.
Warning: Apply shape-rendering only to the specific <rect> or a grouping element <g>. Applying it to the root <svg> will make text and diagonal lines look jagged.
The clipPathUnits Pitfall
When using a <rect> inside a <clipPath>, the clipPathUnits attribute changes how coordinates are interpreted. The default is userSpaceOnUse (absolute pixels), but many developers switch to objectBoundingBox for responsive clipping.
In objectBoundingBox mode, the coordinate system is normalized to a 0 to 1 scale. A common mistake is defining a rectangle with width="100" inside this path. Because 100 is far greater than 1.0, the clip region often extends far beyond the target element or behaves unpredictably, often resulting in the element disappearing entirely if the coordinates are shifted.
Worked Example: Responsive Rounded Box with Crisp Edges
<svg width="200" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<!-- A rectangle with a 1px stroke, snapped to pixels -->
<rect
x="10.5"
y="10.5"
width="180"
height="70"
rx="10"
ry="10"
stroke="black"
stroke-width="1"
fill="lightblue"
shape-rendering="crispEdges"
/>
</svg>
Trade-offs and Limitations
| Feature | Limitation | Workaround |
|---|---|---|
| Stroke Alignment | Always centered; no 'inner' or 'outer' stroke. | Inset the rect geometry by half the stroke-width. |
| Non-uniform Scaling | rx/ry do not scale proportionally with transform="scale()". |
Use <path> with relative elliptical arc commands. |
| BBox Calculation | Chrome and Firefox differ on whether getBBox() includes stroke. |
Use getBoundingClientRect() for visual bounds. |
Verification Checklist
To ensure your implementation is correct, run these checks in your browser DevTools:
- Radius Check: Set
rx="100%"on a non-square rectangle. Verify that the corners are clamped to 50% of the width/height rather than extending beyond the shape. - Edge Check: Zoom into a 1px stroke at 400%. If the line looks fuzzy, verify that
shape-rendering="crispEdges"is applied or that coordinates are offset by 0.5px. - Clip Check: If using
objectBoundingBox, ensure allx, y, width, heightvalues are between 0 and 1.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.