Skip to content
JavaScript js canvas 4 min read

Drawing Rectangles

Rectangles are the only shape the Canvas 2D API can draw with a single dedicated method call — no paths required. That makes them the perfect starting point for learning how the context renders, styles, and erases pixels. Once you understand fillRect, strokeRect, and clearRect, the same fillStyle, strokeStyle, and lineWidth properties carry over to every other drawing operation you’ll ever do on a canvas.

The three rectangle methods

The rendering context exposes exactly three rectangle methods, each taking the same four arguments: an x and y for the top-left corner, plus a width and height. Coordinates are measured in pixels from the top-left of the canvas, with the y-axis pointing down.

MethodWhat it does
fillRect(x, y, w, h)Paints a solid rectangle using the current fillStyle
strokeRect(x, y, w, h)Outlines a rectangle using the current strokeStyle and lineWidth
clearRect(x, y, w, h)Erases the pixels in the region, making them transparent

Unlike path-based drawing, these methods draw immediately — there is no beginPath() or fill() step to call afterward.

const canvas = document.querySelector("#scene");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "#2563eb";
ctx.fillRect(20, 20, 120, 80); // solid blue box

ctx.strokeStyle = "#dc2626";
ctx.lineWidth = 4;
ctx.strokeRect(170, 20, 120, 80); // red outlined box

Setting fill and stroke styles

fillStyle controls the color used by fillRect, while strokeStyle controls the outline color used by strokeRect. Both accept any valid CSS color string — named colors, hex, rgb(), rgba(), or hsl() — and both default to opaque black (#000000).

These properties are stateful: once you set them, every subsequent draw call uses that value until you change it again. Set the style first, then draw.

ctx.fillStyle = "rgba(34, 197, 94, 0.5)"; // semi-transparent green
ctx.fillRect(0, 0, 100, 100);

ctx.fillStyle = "hsl(280 80% 60%)"; // purple
ctx.fillRect(120, 0, 100, 100);

Tip: A common beginner bug is calling fillRect before setting fillStyle, then wondering why everything is black. Because state persists, always set the style on the line directly above the draw call until the order becomes second nature.

Controlling outline thickness

lineWidth sets the stroke thickness in pixels for strokeRect (and all path strokes). The line is centered on the rectangle’s edge, so half of the width sits inside the bounds and half sits outside. A lineWidth of 10 on a 100×100 rectangle therefore covers from -5 to 105 along each edge.

ctx.strokeStyle = "#111827";

ctx.lineWidth = 1;
ctx.strokeRect(20, 20, 80, 80);

ctx.lineWidth = 8;
ctx.strokeRect(140, 20, 80, 80);

Warning: Thin strokes can look blurry because the centered line straddles two pixel rows. For a crisp 1px border, offset the coordinates by half a pixel — e.g. strokeRect(20.5, 20.5, 80, 80).

Erasing with clearRect

clearRect does the opposite of filling: it resets the pixels in a region to fully transparent, revealing whatever is behind the canvas (the page background, by default). This is the standard way to wipe the canvas before each frame of an animation.

// Clear the entire canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);

You can also clear a smaller region to “punch a hole” in an existing fill, which is handy for masking or simple sprite erasing.

A complete interactive demo

The pen below combines all three methods plus styling. It draws a backdrop, an outlined frame, and then clears a window out of the fill.

<canvas id="demo" width="320" height="200" style="background:#0f172a"></canvas>
<script>
  const ctx = document.getElementById("demo").getContext("2d");

  // Solid fill
  ctx.fillStyle = "#38bdf8";
  ctx.fillRect(40, 30, 240, 140);

  // Outline on top
  ctx.strokeStyle = "#f97316";
  ctx.lineWidth = 6;
  ctx.strokeRect(40, 30, 240, 140);

  // Erase a window in the middle
  ctx.clearRect(120, 70, 80, 60);
</script>

Building a grid

Because the rectangle methods are so cheap, they’re ideal for generating repeating layouts like grids, checkerboards, or bar charts. Loop over rows and columns and compute each cell’s position.

<canvas id="grid" width="320" height="320"></canvas>
<script>
  const ctx = document.getElementById("grid").getContext("2d");
  const cols = 8;
  const cell = 320 / cols;

  for (let row = 0; row < cols; row++) {
    for (let col = 0; col < cols; col++) {
      const isDark = (row + col) % 2 === 0;
      ctx.fillStyle = isDark ? "#1e293b" : "#e2e8f0";
      ctx.fillRect(col * cell, row * cell, cell, cell);
    }
  }
</script>

When you run a grid like this, no console output is produced — the result is purely visual. If you log the cell size for debugging:

console.log(`Each cell is ${320 / 8}px square`);

Output:

Each cell is 40px square

Best Practices

  • Set fillStyle, strokeStyle, and lineWidth before the draw call, and remember they persist across calls until reassigned.
  • Use clearRect(0, 0, canvas.width, canvas.height) to wipe the whole canvas at the start of each animation frame.
  • Offset coordinates by 0.5 pixels when you need a sharp 1px border to avoid anti-aliasing blur.
  • Draw fills first and strokes second so the outline sits crisply on top of the solid color.
  • Avoid resetting styles you haven’t changed — minimizing state changes keeps rendering fast in tight loops.
  • Cache canvas.getContext("2d") in a variable rather than calling it repeatedly.
Last updated June 1, 2026
Was this helpful?