SVG rect: borders, rounded corners and one-sided strokes
An SVG rect has no border or border-radius: the border is its stroke and the corners are rx and ry. Borders on one side, inside borders and centring a rect.
Published
An SVG <rect> takes no notice of the CSS border and border-radius properties. Its border is its stroke, set with stroke and stroke-width, and its rounded corners come from the rx and ry attributes.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60">
<rect x="10" y="10" width="80" height="40" rx="8"
fill="gold" stroke="teal" stroke-width="4"/>
</svg>
A gold rectangle with a teal border 4 units thick and corners rounded to a radius of 8.
From CSS to SVG
| In CSS you would write | On a rect, write |
|---|---|
border: 4px solid teal | stroke="teal" stroke-width="4" |
border-radius: 8px | rx="8" |
border-style: dashed | stroke-dasharray="6 3" |
background: gold | fill="gold" |
border-top only | stroke-dasharray, as shown below, or a separate <line> |
box-sizing: border-box | No equivalent: shrink the rect by the stroke width |
Tested in Chrome, style="border: 4px solid teal; border-radius: 10px" on a rect changed nothing at all. stroke and stroke-width can be written as attributes or as CSS, since both are also CSS properties.
The border sits half outside
A stroke is centred on the edge of the shape. With stroke-width="4", 2 units fall inside the rectangle and 2 outside. The rect above has its edge at x = 10, so its border covers x = 8 to x = 12, and the whole painted shape is 84 by 44, not 80 by 40.
This matters in two places. A rect that touches the edge of the viewBox loses the outer half of its border, which is clipped away. And a border meant to be 4 units wide inside an 80 by 40 box needs the rect made smaller by hand:
<rect x="12" y="12" width="76" height="36" fill="gold" stroke="teal" stroke-width="4"/>
The rect is moved in by half the stroke width on every side, so the outer edge of the border lies exactly on the 80 by 40 box that starts at 10,10. This is the SVG way of writing box-sizing: border-box.
When the size of the rect cannot change, double the stroke width and clip the shape to its own outline, which hides the outer half. Fill and stroke shows that method.
A border round the whole drawing
A frame round the whole SVG is the case where the clipped half shows most. This looks right and is not:
<rect width="100%" height="100%" fill="none" stroke="teal" stroke-width="4"/>
Only 2 of the 4 units are visible, because the rest lies outside the viewBox. Two fixes:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="2" y="2" width="96" height="96" fill="none" stroke="teal" stroke-width="4"/>
</svg>
Or, for an SVG that is inline in a web page, leave the drawing alone and put a CSS border on the <svg> element itself, which is an ordinary box on the page:
<svg viewBox="0 0 100 100" width="400" height="400"
style="border: 4px solid teal; box-sizing: border-box">
<circle cx="50" cy="50" r="50" fill="gold"/>
</svg>
Without box-sizing: border-box the border is added to the size, and this element measures 408 pixels across instead of 400.
Rounded corners: rx and ry
rx is the horizontal radius of each corner and ry the vertical one. Give one and the other takes the same value, so rx="8" makes plain round corners. Give both, with different values, for corners that are quarter ellipses.
All four corners are always the same. The SVG 2 specification says so directly: for uneven rounding, “define the shape explicitly with a path”.
The radius is capped, and the cap works differently from CSS. Each radius is limited separately: rx to half the width and ry to half the height. So an oversized value does not make a pill shape, as border-radius: 999px does in CSS. It makes an ellipse:
<rect x="10" y="10" width="80" height="40" rx="20"/>
<rect x="10" y="60" width="80" height="40" rx="100"/>
The first is a pill: flat top and bottom, half-circle ends. In the second, ry copies the 100, then rx is cut down to 40 and ry to 20, and the result is a full ellipse with no straight sides. For a pill, set rx to exactly half the shorter side. A percentage does not help either: rx="50%" is measured against the width and gives the same ellipse.
In Chrome rx and ry also work as CSS properties, so rect { rx: 10px } in a style sheet rounds the corners. Programs other than browsers generally read only the attribute.
Rounding only some corners
This needs a <path>. Each rounded corner is one arc command. A rectangle from 10,10 to 90,50 with only its top corners rounded to a radius of 10:
<path d="M 10 50 V 20 a 10 10 0 0 1 10 -10 H 80 a 10 10 0 0 1 10 10 V 50 Z" fill="teal"/>
Read it as: start at the bottom left, go up to 10 below the top, arc round to the top edge, run along it, arc down, go down the right side and close. To round a different corner, replace the sharp turn at that corner with the same kind of arc. The SVG path guide explains the arc command, and the path editor shows the points as you move them.
A border on one side only
There is no stroke-top. The stroke of a rect is one continuous line, and the SVG specification fixes its route: it starts at the top-left corner and runs along the top, down the right side, back along the bottom and up the left side. A dash pattern can therefore switch whole sides on and off.
For a rect of width w and height h with square corners:
| Side | stroke-dasharray | For 80 by 40 |
|---|---|---|
| Top | w, 2h + w | 80 160 |
| Right | 0, w, h, w + h | 0 80 40 120 |
| Bottom | 0, w + h, w, h | 0 120 80 40 |
| Left | 0, 2w + h, h | 0 200 40 |
| Top and bottom | w, h | 80 40 |
<rect x="10" y="10" width="80" height="40" fill="gold"
stroke="teal" stroke-width="4" stroke-dasharray="0 120 80 40"/>
A gold box with a teal line along the bottom only. The numbers are dash, gap, dash, gap: a dash of 0, a gap of 120 that skips the top and the right side, a dash of 80 along the bottom, and a gap of 40 for the left side.
The sums only work with square corners, since rounding shortens the outline. If the rect may change size, a separate <line> along that side is simpler to keep right. Dashed lines covers stroke-dasharray in full.
A border that keeps its thickness
Stroke width is in the drawing’s units, so it grows and shrinks with the SVG. To keep a hairline at the same thickness on screen at any size, add vector-effect="non-scaling-stroke":
<rect x="10" y="10" width="80" height="40" fill="none"
stroke="teal" stroke-width="2" vector-effect="non-scaling-stroke"/>
Drawn at four times its natural size in Chrome, this border measured 2 pixels, not 8.
Centring a rect
x and y are the top-left corner, so centring is subtraction: x is half the viewBox width less half the rect’s width, and the same for y. In a viewBox of 0 0 200 100, an 80 by 40 rect is centred at x="60" y="30".
To avoid the sum, put the corner at the middle with percentages and shift the rect back by half its own size:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect x="50%" y="50%" width="80" height="40" fill="teal" transform="translate(-40 -20)"/>
</svg>
In a web page, CSS can do the second step without knowing the size: transform-box: fill-box; transform: translate(-50%, -50%) on the rect moved it by half its own width and height when tested in Chrome.
One rect inside another
A <rect> cannot hold other shapes. A shape written between <rect> and </rect> is not drawn: the element is only allowed to contain things such as a <title> or an animation. To put one box inside another, write them one after the other and let the second be painted on top, or wrap both in a <g> so they move together:
<g transform="translate(10 10)">
<rect width="80" height="80" fill="gold"/>
<rect x="10" y="10" width="20" height="20" fill="teal"/>
</g>
The positions of both are then measured from the group’s corner. SVG g and use covers groups.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| The rect does not show | No width or height, or one of them is 0 | Give both. Unlike a <div>, a rect has no size from its contents |
| The rect does not show | fill="none" and no stroke | Add a stroke |
| The rect does not show | A negative width or height: Chrome reports “A negative value is not valid” in the console | Move x or y instead and keep the size positive |
| The border is thinner on some sides | The rect touches the viewBox edge and half the stroke is clipped | Move the rect in by half the stroke width |
border or border-radius does nothing | They do not apply to SVG shapes | stroke and rx |
A large rx gives an oval | rx and ry are capped separately | Set rx to half the shorter side |
| Only one corner should be round | A rect rounds all four alike | Use a <path> |
| A shape inside the rect is missing | A rect cannot have child shapes | Make them siblings, in a <g> |
Questions
Is there a square element? No. A square is a rect with equal width and height. SVG shapes lists all six basic shapes.
Can the border go fully outside the rect? Not with a setting. Make the rect larger by the stroke width, or paint the stroke first with paint-order="stroke" so that the fill covers the inner half.
Can I put text in a rect? Not inside the element. Write a <text> after the rect at the same place: SVG text shows how to centre it.
Do cutting machines follow the border? Most cutting software follows the outline of the rect itself, which is the middle of the stroke, and ignores the stroke width. A border that should be cut out as a band has to be drawn as two outlines, or converted from a stroke into a shape in a vector editor first.