Canvas rect() Draws Nothing Until You Call fill()
Canvas rect() defines a path but does not paint until fill(), stroke(), or clip(). Learn when to use it and how to debug invisible rectangles.
04 Feb 2026, 07:15 UTC

The blank rectangle problem
You call ctx.rect(20, 20, 80, 60) and expect a rectangle. The canvas stays blank. This is not a bug. CanvasRenderingContext2D.rect() is a path-building method: it appends a rectangular sub-path to the current path, but it does not paint pixels. You must follow it with fill(), stroke(), or clip() to make anything visible.
The useful takeaway is simple: use rect() when you want to group several rectangles into one path and paint them with a single call. Use fillRect() or strokeRect() when you need immediate pixels with no extra step.
rect() versus fillRect() and strokeRect()
fillRect() and strokeRect() are immediate-mode operations. They change pixels right away. rect() only records geometry. The current path accumulates until you call beginPath() or paint it.
| Method | Paints immediately? | Best for |
|---|---|---|
fillRect(x, y, w, h) | Yes | Single solid rectangle |
strokeRect(x, y, w, h) | Yes | Single outlined rectangle |
rect(x, y, w, h) then fill() | No, until fill() | Many rectangles sharing one style |
Negative width and height, plus transforms
rect() accepts negative width or height. A negative width extends left from the starting x; a negative height extends upward from y. The current transformation matrix also applies: scale(), rotate(), and translate() change where the rectangle lands and how large it appears. If your rectangle is in the wrong place or the wrong size, check the transform state before blaming rect().
Worked example: one rectangle, then several at once
<canvas id="demo" width="300" height="150"></canvas>
<script>
const canvas = document.getElementById('demo');
const ctx = canvas.getContext('2d');
// This call alone draws nothing.
ctx.rect(20, 20, 80, 60);
// Paint the recorded path.
ctx.fillStyle = '#4a90e2';
ctx.fill();
// Reset the path, then stack two rectangles before painting.
ctx.beginPath();
ctx.rect(20, 90, 60, 40);
ctx.rect(100, 90, 60, 40);
ctx.fill();
</script>
Where to run: save the snippet as an HTML file and open it in any modern browser, or paste the script into the browser console after a canvas exists. Permissions: none. Expected check: a blue rectangle appears at (20, 20), followed by two more blue rectangles at y = 90. Risk: if you omit fill() after the first rect(), nothing renders, which is the most common source of confusion.
Limitation: there is no path inspector
Canvas 2D does not expose a way to list or inspect the current path. You cannot programmatically confirm that rect() added a sub-path without painting it. The practical workaround is to temporarily add ctx.fill() or ctx.stroke() after rect() to verify the path exists, then remove or keep the paint call as needed. There is no rollback for canvas pixels; use clearRect() to erase an area if you need to start over.
Quick checklist
- Did you call
fill(),stroke(), orclip()afterrect()? - Did you call
beginPath()if you want separate rectangle groups? - Are
scale(),rotate(), ortranslate()shifting the rectangle unexpectedly? - Would
fillRect()be simpler for a single rectangle?
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.