Why SVG is different
SVG is an image format like .jpg or .gif; you can drop one in an <img> tag and it behaves the way you’d expect. The more interesting move is to inline the raw SVG markup directly into your HTML.
Unlike bitmap formats, SVG doesn’t store per-pixel color data. It’s written in XML and holds the drawing instructions needed to render an illustration. That makes it structurally similar to HTML, but with a primitive set built for drawing: <circle>, <polygon>, <path>, and friends instead of <div> and <p>.
The payoff is that inline SVG nodes are first-class DOM citizens. CSS and JavaScript can select and mutate them like any HTML element. Many SVG presentation attributes — such as fill for color and r for radius — also work as CSS properties. That opens up transitions and animations with plain CSS, which is what makes SVG feel like an alternate-reality HTML built for illustration rather than documentation.
The core shape primitives
Your drawing vocabulary starts with a handful of shape elements. Each has its own geometry model, and the edges behave differently than you might expect from CSS box styling.
Lines and rectangles
A <line> is defined by two endpoints: x1/y1 for the start and x2/y2 for the finish. This is something HTML can’t do cleanly — drawing a diagonal line with DOM elements typically means creating a thin rotated node and solving an awkward geometry problem to position it.
A <rect> is positioned by its top-left corner via x and y, then sized with width and height. There are three notable differences from an HTML <div> with a border:
- The stroke is always painted on the center of the path, never on the inside or outside edge — and this isn’t configurable per shape.
- If either dimension is 0, the entire shape disappears. The spec calls these zero-area shapes “degenerates,” and while browser behavior used to be inconsistent, all modern browsers now drop them entirely.
- Corners can be rounded with
rxandry, mirroring whatborder-radiusdoes for HTML elements. In fact, SVG uses the same two-axis radius model thatborder-radiusdoes.
Circles, ellipses, and polygons
A <circle> is positioned by a center point (cx, cy) and sized by a single radius r. An <ellipse> adds independent horizontal and vertical radii (rx and ry) to produce ovals. Like rectangles, circles with a zero radius will not render.
For multi-sided shapes, the <polygon> element takes a points attribute — a flat list of X/Y coordinates — and connects them in order, closing back to the start. Note that “polygon” in SVG doesn’t imply regularity; regular polygons (triangles, hexagons, etc.) require trigonometry to compute their vertex coordinates. The generic element handles any irregular, concave, or arbitrary multi-sided shape you can define.
The viewBox coordinate system
If you use raw pixel coordinates for everything, your SVG will be fixed to a physical size. Shrink the container and you get cropping, not scaling — which is unlike how bitmap images behave.
The fix is the viewBox attribute. It establishes an internal coordinate system so that your shapes are defined in abstract units, not DOM pixels. The SVG’s width and height (or its CSS size) determines the rendered size independently of those internal coordinates.
The attribute takes four numbers that you can think of as two value pairs:
- The first pair sets the position of the visible window. By changing these, you pan across the infinite SVG canvas — for example, to only view a specific region of a large chart.
- The second pair sets the size of the visible window. Changing these alters the zoom level. A
viewBox="0 0 300 300"on a 300px square SVG gives a 1:1 ratio between internal and DOM coordinates. AviewBox="0 0 150 150"on the same SVG produces a 2x zoom on the shapes.
In practice, you rarely animate or change the viewBox. Its power is in keeping the values static so the same SVG scales cleanly across layouts: define one internal coordinate space, then render it at whatever pixel size the context requires. Once the internal system is in place, your shapes automatically scale to fill whatever box the SVG occupies.
Styling SVG Shapes with Fills and Strokes
SVG elements can be colored in two primary ways: using the fill attribute to paint the interior, or the stroke attribute to outline the shape. Both can be applied simultaneously.
While fill is straightforward, stroke properties offer significantly more flexibility than standard HTML borders. You can control the appearance of a stroke using either CSS properties or inline SVG attributes (for instance, stroke-width: 5px in CSS is equivalent to stroke-width="5" in the markup).
<style>
circle {
stroke: hsl(45deg 100% 50%);
stroke-width: 6px;
stroke-dasharray: 20, 14;
stroke-linecap: butt;
}
</style>
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="50" />
</svg>
The primary stroke-related properties are:
stroke— Defines the color of the outline. Its default value istransparent.stroke-width— Sets the thickness of the outline in pixels.stroke-dasharray— Accepts a list of lengths that define the dash and gap pattern. For example,10, 20creates a 10px dash followed by a 20px gap. Adding more values creates a repeating pattern, such as5, 5, 10, 5.stroke-linecap— Determines how the ends of each dash are rendered. The default value,butt, produces flat ends. Theroundvalue adds circular caps, whilesquareadds rectangular caps. This becomes particularly visible when the stroke width is thick or the dash length is very short.
Animating Strokes with CSS
Because stroke properties like stroke-width are also valid CSS properties, you can animate them just like any other CSS value. This allows for smooth transitions between different styles, as shown in the demo above, using basic CSS transitions.
circle {
transition:
stroke 1200ms,
stroke-width 900ms,
stroke-dasharray 1500ms,
stroke-linecap 1000ms;
}
A particularly powerful property for animation is stroke-dashoffset. This property shifts the starting point of the dash pattern, effectively sliding the dashes around the perimeter of the shape. This single trick enables a variety of effects:
Marquee effects: By animating the offset, dashes can appear to run continuously around the element, similar to a "marquee" light. For a seamless loop, the
stroke-dashoffsetvalue should ideally equal the combined length of one dash plus its following gap to prevent a visible jump. The gap size often needs tuning to repeat cleanly across the shape’s circumference.Spinners: Combining animated dash length with an animated offset creates the illusion of a rotating load indicator—even when nothing is actually loading.
Line-drawing effect: Perhaps the most well-known trick, this creates the illusion of a shape drawing itself. The technique involves a single dash whose length matches the shape’s total circumference, followed by an enormous gap. By animating the
stroke-dashoffset, the dash is "slid" into place to reveal the path.
Determining the exact circumference of a path for these effects can be done programmatically for precision. A JavaScript snippet can be used to measure the total length of any given path.
const element = document.querySelector('polygon');
// 👇 This is the magical method that calculates the circumference:
const pathLength = element.getTotalLength();
element.style.strokeDasharray = `${pathLength}, 1000`;
While scripting provides the precise value, a trial-and-error approach—guesstimating the length until the animation looks correct—can also be effective in practice.
Beyond the Basics
This overview has covered the foundational elements of SVG and a few straightforward techniques. However, the full capabilities of SVG extend far beyond basic fills, strokes, and their respective animations. Path manipulation, masks, clipping, and sophisticated transform-based animations all build upon these core principles to unlock a much wider range of creative applications in web development.



