Styling with CSS and paint servers
Presentation attributes versus CSS rules, fill and stroke semantics, gradients and markers, and transforms that respect the element's own box.
Attributes or CSS?
<svg viewBox="0 0 200 100" class="chart">
<polyline class="series" points="0,80 50,40 100,60 150,10 200,30" />
</svg>
<style>
.chart { overflow: visible; }
.series {
fill: none;
stroke: currentColor; /* inherits the CSS color of the element */
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke; /* keeps 2px at any scale */
}
.chart:hover .series { stroke: #f97316; }
</style>SVG presentation attributes are CSS properties with the lowest possible priority — any matching CSS rule overrides them. That makes attributes the right place for per-instance data from a template, and CSS the right place for theme, hover and dark-mode styling.
| Property | Applies to | Notes |
|---|---|---|
fill | Closed shapes, text | none switches it off; currentColor follows CSS text colour |
stroke / stroke-width | Any shape or path | Stroke is centred on the outline and can be clipped by the viewport |
stroke-dasharray | Strokes | Dash pattern; also the basis of the draw-on animation |
opacity | Any element | Group opacity composites the group, unlike per-shape opacity |
fill-rule | nonzero or evenodd | Decides which regions of a self-overlapping path are filled |
paint-order | Text | stroke fill keeps a text outline from cutting into the glyphs |
Gradients, patterns and markers
<svg viewBox="0 0 200 100">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#1d4ed8" />
</linearGradient>
<pattern id="hatch" width="8" height="8" patternUnits="userSpaceOnUse">
<path d="M0 8 L8 0" stroke="#94a3b8" stroke-width="1" />
</pattern>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M0 0 L10 5 L0 10 z" fill="#0f172a" />
</marker>
</defs>
<rect x="10" y="10" width="180" height="50" fill="url(#sky)" />
<rect x="10" y="70" width="180" height="20" fill="url(#hatch)" />
<line x1="10" y1="55" x2="170" y2="55" stroke="#0f172a"
stroke-width="2" marker-end="url(#arrow)" />
</svg>gradientUnitsdefaults toobjectBoundingBox, so the gradient stretches to each element it paints;userSpaceOnUseshares one gradient across shapes.- A
markerwithorient="auto-start-reverse"can be used for both the start and end of a line. - Patterns tile in user space — a stroke-heavy pattern on a large area is a rendering cost, not a free texture.
Transforms, filters and clipping
<style>
.rot { transform: rotate(15deg); transform-box: fill-box; transform-origin: center; }
.soft { filter: url(#blur); }
</style>
<svg viewBox="0 0 200 120">
<defs>
<filter id="blur" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" />
</filter>
<clipPath id="half"><rect x="0" y="0" width="100" height="120" /></clipPath>
</defs>
<rect class="rot" x="20" y="20" width="60" height="60" fill="#a5b4fc" />
<circle class="soft" cx="140" cy="60" r="35" fill="#fbbf24" />
</svg>| Concept | Syntax | Remember |
|---|---|---|
| Transform | transform="translate(10 5) rotate(30)" | Space-separated transforms, no commas between functions |
| CSS transform origin | transform-box: fill-box | Without it, percentages resolve against the viewport, not the shape |
| Filter region | x y width height on filter | Defaults to -10%/+120%; blur and drop shadow get clipped without a larger region |
| Clip path | clip-path="url(#id)" | Hard edge, no partial transparency |
| Mask | mask="url(#id)" | Luminance-based, so it fades — and it is more expensive than clipping |
⚠️
transform-box: fill-box is what makes transform-origin: center mean the shape's own centre. In older WebKit builds this is not honoured, so if a rotation looks anchored to the wrong point, animate the transform attribute instead of the CSS property.FAQ
Why is my CSS not applying to the SVG?
External stylesheet rules do not cross into an SVG referenced by
<img> or background-image, because it is a separate document with no scripting or styling context. Inline the SVG when you need CSS to reach the shapes.How do I get crisp 1px lines?
Use
vector-effect: non-scaling-stroke together with a half-pixel offset, such as y="10.5" for a horizontal line, so the stroke lands on the pixel grid instead of straddling two rows.Related
Shapes and coordinates Selectors and specificity
Last refreshed 2026-09-18.