Skip to content
HomeBlogviewBox and viewport
Fundamentals

SVG viewBox and viewport: How Vector Scaling Actually Works

SM
SVG Maker AI
June 24, 2026
6 min read

An SVG has two rectangles, and nearly every scaling problem is the two of them being confused for each other. The viewport is the space the graphic occupies on your page. The viewBox is the window onto the drawing inside it. Once that relationship is clear, one file scales to a 16-pixel favicon and a full-bleed header without editing a single coordinate — and the art stops arriving cropped, off-centre, stretched or invisible.

1. The viewport is on the page; the viewBox is in the file

The viewport is set from outside the drawing — by width and height on the root element, or by CSS, or by whatever container the SVG is sitting in. Measured in CSS pixels, it is the hole in your layout.

The viewBox is set from inside. It names a rectangle in the drawing's own coordinate system and says: map this rectangle onto the viewport, whatever size the viewport turns out to be. That mapping is the entire mechanism. Change either box and the drawing rescales; change the shapes and it does not.

two_boxes.svg
<svg viewBox="0 0 100 100" width="240" height="240"
     xmlns="http://www.w3.org/2000/svg">

  <!-- viewport : 240 x 240 CSS pixels of page
       viewBox  : 100 x 100 user units of drawing
       scale    : 240 / 100 = 2.4 -->

  <circle cx="50" cy="50" r="40" fill="#facc15" />
  <rect x="10" y="10" width="80" height="80"
        fill="none" stroke="#f97316" stroke-width="2" />
</svg>

A circle at r="40" is not forty of anything in particular. It is forty units of a hundred-unit-wide window, and the window has been stretched to 240 pixels — so it paints at 96 pixels across. Nothing in the markup knew that in advance.

2. What the four numbers do — including the two that get ignored

viewBox="min-x min-y width height"
  • min-x, min-y: where the window sits. Raising them slides the window right and down, which moves the artwork left and up.
  • width, height: how much of the coordinate space the window covers. Smaller numbers mean a bigger drawing, because less of it is being stretched across the same viewport.

Most tutorials write 0 0 for the first pair and move on. That is half the attribute discarded. Together the four numbers are a pan-and-zoom control that costs nothing to use: crop one icon out of a sheet of twenty, or frame a detail, without touching a path.

crop_and_zoom.svg
<!-- The whole drawing. -->
<svg viewBox="0 0 100 100"> ... </svg>

<!-- Same artwork, untouched. Half the window, so what
     remains is drawn at twice the size: a zoom. -->
<svg viewBox="0 0 50 50"> ... </svg>

<!-- Same zoom, window moved to the bottom-right
     quarter: a pan. -->
<svg viewBox="50 50 50 50"> ... </svg>

3. The numbers have no unit, and that is the point

Coordinates inside an SVG are user units. They are not pixels, millimetres or points — they are ratios waiting for a scale factor, and the viewBox supplies it. This is why viewBox="0 0 24 24" is the icon convention: 24 is a convenient grid to draw on, not a promise about size.

A practical consequence catches people out with strokes. stroke-width="2" in a 24-unit viewBox is a heavy line; the same value in a 1000-unit viewBox is a hairline that disappears. When two icons from different sources look mismatched, the usual cause is not the stroke value — it is that they were drawn on different grids.

4. viewBox versus width and height

This is the question that fills the Stack Overflow threads, and the short answer is that they are not alternatives. They answer different questions, and what you get depends on which are present:

  • Both: a fixed size on the page, with a defined coordinate space inside. Predictable, but it will not respond to its container.
  • viewBox only: no intrinsic size, but a known aspect ratio. CSS decides the size and the ratio holds. This is the responsive case.
  • width and height only: a fixed-size viewport with the coordinate system pinned 1:1 to it. It will not scale down for a narrow screen — it overflows at full size, or gets clipped by whatever contains it.
  • Neither: the browser falls back to a default, historically 300 x 150. Rarely what anyone wanted.

5. The responsive recipe

Drop width and height from the markup, keep the viewBox, and let CSS size the container. The browser reads the ratio out of the viewBox and works out the height on its own.

banner.svg
<!-- No width. No height. The viewBox alone gives the
     browser an intrinsic 2:1 ratio to work from. -->
<svg viewBox="0 0 240 120" class="banner"
     xmlns="http://www.w3.org/2000/svg">
  <rect width="240" height="120" rx="8" fill="#f97316" />
</svg>
banner.css
.banner {
  width: 100%;
  height: auto;   /* let the 2:1 ratio set the height */
  display: block; /* SVG is inline by default, which
                     leaves a few px of baseline gap */
}

The height: auto is what stops the graphic being squashed, and the display: block removes the few pixels of stray gap that an inline element gets under its baseline — the phantom padding people spend an afternoon hunting in dev tools.

One caveat worth knowing before you strip the attributes: this applies to SVG written inline in your HTML. A file loaded through <img> with no intrinsic size is at the mercy of the img element's own sizing, and older layout code sometimes needs the dimensions back to reserve space.

6. preserveAspectRatio: what happens when the ratios disagree

A square viewBox in a wide viewport leaves a decision to make, and preserveAspectRatio is where it is made. The value has two parts: an alignment, and a fit.

  • Alignment: xMin, xMid or xMax joined to YMin, YMid or YMax — nine combinations, deciding which edge or corner the drawing hugs.
  • meet: scale until the whole drawing fits. Leftover space stays empty. This is the default.
  • slice: scale until the viewport is filled. The overflow is cropped. This is object-fit: cover, for backgrounds.
  • none: abandon the ratio and stretch to fit. Circles become ellipses and strokes thicken on one axis.
fitting.svg
<!-- Every example below: a 300 x 100 viewport
     holding a square 100 x 100 viewBox. -->

<!-- Default. Fits entirely, centred, 100 px wide,
     with 100 px of empty space either side. -->
<svg preserveAspectRatio="xMidYMid meet">

<!-- Same fit, pushed to the left edge instead. -->
<svg preserveAspectRatio="xMinYMid meet">

<!-- Fills the box. Scales to 300 px and crops the
     top and bottom off. -->
<svg preserveAspectRatio="xMidYMid slice">

<!-- Fills the box by stretching. 3x wide, 1x tall:
     circles become ellipses. -->
<svg preserveAspectRatio="none">

none has a legitimate use — a gradient wash or a wave divider that is meant to stretch — but it is also the setting that quietly distorts a logo, because the distortion only appears at container widths nobody tested.

7. Animating the window instead of the artwork

Because the viewBox is a window, changing it over time gives you a camera: zoom into a diagram, pan across a map, reveal a detail. The artwork is never touched, so it stays crisp at any magnification.

The catch is that viewBox is an attribute rather than a CSS property, so it is invisible to transitions and to element.animate(). Either interpolate the numbers yourself, or use SMIL's <animate attributeName="viewBox">, which does handle it and keeps working inside a standalone file.

zoom.js
// viewBox is an attribute, not a CSS property, so a CSS
// transition will not touch it and neither will
// element.animate(). Interpolate the four numbers yourself.
const svg  = document.querySelector('#map')
const from = [0, 0, 100, 100]
const to   = [50, 50, 50, 50]
const t0   = performance.now()

function step(now) {
  const t = Math.min((now - t0) / 600, 1)
  const eased = t * (2 - t)
  const box = from.map((v, i) => v + (to[i] - v) * eased)
  svg.setAttribute('viewBox', box.join(' '))
  if (t < 1) requestAnimationFrame(step)
}
requestAnimationFrame(step)

8. When it goes wrong, in order of likelihood

  • Art is cut off: shapes sit outside the viewBox. Check the real bounds — an editor's artboard and the exported viewBox are not always the same rectangle.
  • Art is tiny in a corner: the viewBox is much larger than the drawing, usually a leftover from a canvas that was cropped after the fact.
  • It refuses to scale: there is no viewBox, only width and height. Nothing in CSS can fix this; the attribute has to be added.
  • It renders at nothing: a percentage width with no viewBox leaves the intrinsic size undefined. Some renderers resolve that to zero.
  • It stretches: a preserveAspectRatio="none" inherited from a template, or width and height forced through CSS in a ratio the viewBox does not share.
  • A tool rejects the file outright: Figma and most importers need the size to be resolvable. A missing viewBox with no usable width and height is the most common flat refusal.

Where to go next

Our exporter treats this as non-negotiable: every SVG that leaves the editor is parsed first, and gets a viewBox, an explicit width and height, and the xmlns declaration written onto the root element — derived from the viewBox where one exists, and synthesised from the dimensions where it does not. Markup that will not parse is refused rather than exported broken. Worth knowing: because the export also writes width and height, an inline copy destined for the responsive recipe in section 5 needs those two attributes removed afterwards. The optimiser rounds viewBox numbers to your chosen precision but never strips the attribute.

If the file is bound for a design tool, the Figma import guide covers the other refusals worth pre-empting. To watch the coordinates change as you drag an anchor — the fastest way to make user units stop feeling abstract — open the editor.