Topic 1.5
Styling SVG and Text
In one line
SVG shapes are coloured with fill (inside) and stroke (outline), set either as attributes or in CSS, where CSS wins. Text is placed by its baseline and anchor, never wraps, and overflows silently when a label is too long.
Think of it like this
A sign painter. The job sheet says "paint the board red". Then the shop owner leaves a note: "make that blue". The note wins, and a sticker the owner puts on the board itself wins over both. SVG styles work like that: a colour written on the shape is the job sheet, a CSS rule is the owner's note, and an inline style is the sticker.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- fill
- The colour inside a shape.
- stroke
- The line along the edge of a shape. Its width is
stroke-width, centred on the edge. - presentation attribute
- A style written as an attribute on the tag, such as
fill="red". Any CSS rule beats it. - currentColor
- A colour value that means "the CSS
colorof this element". It follows the text colour, for example when a theme changes. - baseline
- The line letters sit on. A
<text>element'syis its baseline. - text-anchor
- Which part of the text sits at
x:start,middleorend. - tspan
- A piece of a
<text>. Use several, each with its own position, to make more than one line.
Step by step
01Find out which colour wins
The first example paints four squares orange with fill="#e76f51". The first has only that attribute. The second adds a class whose CSS rule sets a blue fill. The third also has a style attribute with green. The fourth uses fill="currentColor".
The Console prints the colour the browser really applies. The attribute alone gives orange, the CSS class beats the attribute and gives blue, and the inline style beats both and gives green. Then the page switches to dark mode by adding a class to the body: only the currentColor square changes, because its colour comes from the color property.
<rect fill="#e76f51" ...> <!-- attribute: loses to any CSS -->
<rect fill="#e76f51" class="blue" ...> <!-- .blue { fill: blue } wins over the attribute -->
<rect fill="#e76f51" class="blue" style="fill:green" ...> <!-- style wins over the class -->02A stroke straddles the edge
The wide bar in the first example is at x = 270 with width 100 and a stroke of 12. The browser says its box is 270 to 370 (the geometry), but the ink reaches from 264 to 376: 6 units each side. A bar standing on a baseline at y = 180 with a thick stroke therefore dips 6 units below the line. Chart authors leave stroke off bars, or inset them by half a stroke.
The two squares at the bottom compare opacity and fill-opacity. With fill-opacity, the thick stroke stays solid and the fill is faint. With opacity, the stroke fades too.
03Place text by baseline and anchor
In the second example, three words share x = 150 (the dashed line) with different text-anchor values. The Console reports the part that sits on 150: the left edge for start, the middle for middle and the right edge for end. This is the same number for every font, which is why anchors are safer than guessing widths.
Under that, the same text is drawn with the default baseline, dominant-baseline="middle" and "hanging" at the same y, so you see the three vertical positions on one grey guide line.
04Two lines and one long label
The text block at the right uses two <tspan>s. The second says dy="16": 16 units below the first. Without its own x, a second tspan would continue after the first on the same line.
The long label is placed in an 80-unit slot. getComputedTextLength() says it does not fit. The code shortens it until it fits and ends with …, and stores the full text in a <title> so a hover and a screen reader still get it. The Console says whether each step fits (not the lengths, which depend on the computer's font).
Try it yourself
- 1
Let a class win
In the first example's CSS, add
rect { fill: #264653; }. Now#aprintsrgb(38, 70, 83)instead of orange: a very general CSS rule still beats the attribute.#dgets the same colour, because the CSS rule beats itsfill="currentColor"attribute too. The class (#b) and the inline style (#c) are more specific than a plainrectrule, so they keep winning. Presentation attributes lose to everything. - 2
Right-align a label
In the second example, set
text-anchor="end"on thelongtext and move it tox="388". The label now ends at x = 388, just inside the dashed slot's right edge (390), instead of starting at the left edge, which is how axis labels on the left of a chart are drawn. - 3
Add a stroke to the bar chart's bar
In the first example, change the bar's
stroke-widthfrom 12 to 2. The Console's last line becomes... but the ink is x 329 to 431, and the bar's edges are almost where its geometry says. Thin strokes make geometry and ink agree closely.
Code & diagrams
Every colour printed is what the browser actually uses (getComputedStyle), not what the attribute says. Look at the picture too: the two squares at the bottom compare opacity and fill-opacity.
Preview
Console
Nothing logged yet.Expected console output
attribute only: rgb(231, 111, 81)
attribute + CSS class: rgb(29, 78, 216)
class + inline style: rgb(42, 157, 143)
currentColor (light): rgb(38, 70, 83)
currentColor (dark): rgb(233, 196, 106)
attribute-only square in dark mode: rgb(231, 111, 81) (unchanged: nothing sets color for it)
Bar geometry: x 330 to 430, but the ink is x 324 to 436Only text-independent facts are printed: whether a label fits, and where an anchor sits. Pixel widths depend on the computer's fonts.
Preview
Console
Nothing logged yet.Expected console output
text-anchor left edge is at x = 150
text-anchor centre is at x = 150
text-anchor right edge is at x = 150
Long label fits in the slot: false
Shortened label fits in the slot: true
Full text kept in <title>: Harbour Gate Ferry Terminal (west side)
Real text has 5 characters; the path has 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Style that never shows up
In the first example, change .blue { fill: #1d4ed8; } to .blue { color: #1d4ed8; }, a typo: color instead of fill.
Break #2
Let a label run off the edge
In the second example's HTML, change x="312" of the long label to x="380". The code still shortens the label to fit an 80-unit slot, but the slot now starts too close to the right edge.
Myth vs fact
Myth
fill="red" on the tag always wins because it is closest to the shape.
Fact
A presentation attribute is the weakest style. Any CSS rule beats it, and an inline style beats a class.
Myth
A stroke grows inwards, so it never changes the shape's size.
Fact
A stroke is centred on the edge: half sits outside. A 12-unit stroke adds 6 units on every side.
Myth
SVG text wraps like a paragraph.
Fact
One <text> is one line. Long text runs on and is clipped only at the SVG's edge. For lines, use <tspan> with dy.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
CSS custom properties pierce into inline SVG:
fill: var(--chart-accent)works, and a theme switch that sets--chart-accenton:rootrecolours every chart at once. Phase 10 builds the full set of tokens (--chart-ink,--chart-muted,--chart-grid,--chart-accent,--chart-bg). - ▸
A
<text>element accepts lists forx,yanddx, which place each letter separately, andtextLengthwithlengthAdjustto squeeze text into a width. Both are rarely right for charts: they bypass the font's own spacing. - ▸
text-anchor,dominant-baselineandfont-sizeare presentation attributes and can also be set in CSS.font-sizein an SVG is in user units, so inside aviewBoxthe text scales with the drawing (Topic 1.4). Usepaint-order: stroketo draw a text's outline behind its fill, which gives a halo that keeps labels readable over bars. - ▸
getComputedTextLength()needs the text to be in the document and rendered with its final font. If a web font has not loaded yet, you measure the fallback font and the label may overflow later. Wait fordocument.fonts.readybefore measuring in production code.
Remember this
- 1
The problem this solves: a dashboard needs a light and a dark theme, bars that change colour on hover, text that lines up with axes, and labels that do not run off the edge. You must know which colour setting wins, where a label starts and ends, and what happens when text is too long.
- 2
Fill, stroke and opacity.
fillpaints the inside andstrokepaints a line along the edge.stroke-widthsets that line's thickness in user units. The stroke is centred on the edge: half of it is outside the shape, so a 12-unit stroke on a rectangle makes it look 6 units larger on every side.opacitymakes the whole shape see-through, whilefill-opacityandstroke-opacityact on one part only. A fill ofnoneleaves the inside empty. - 3
Attributes versus CSS: CSS wins.
fill="red"written on a tag is a presentation attribute. It is the weakest style of all: any CSS rule, even a very general one, overrides it. A class rule overrides the attribute, andstyle="fill: ..."on the tag overrides the class. So set colours in CSS for anything a theme or a hover should change, and keep attributes for geometry such asx,yandwidth. The first example prints the colour the browser really uses in each case. - 4
**
currentColorfollows the text colour.**fill="currentColor"orstroke="currentColor"means "use the CSScolorvalue of this element". Changecoloron a parent (for example in dark mode) and every shape usingcurrentColorfollows, with no change to the SVG. In Phase 10 you replace this with CSS custom properties, which can hold many colours at once. - 5
Text is placed by its baseline. In
<text x="150" y="40">,yis the line the letters sit on, not the top of the text, so the letters rise abovey(only descenders such asgandyhang below).text-anchorsets which part of the text sits atx:start(the left edge, default),middle(the centre) orend(the right edge). Axis labels on the left useend; labels under bars usemiddle.dominant-baselinemoves the text vertically:middlecentres it ony, andhanginghangs it fromy. - 6
Text never wraps. One
<text>is one line. For several lines, use<tspan>pieces, each with its ownxand ady(the step down). There is no automatic line breaking and no clipping at a neighbour: text that is too long runs on past its slot and is only cut at the SVG's own edge, silently. Measure it withgetComputedTextLength()and shorten it with…, keeping the full text in a<title>child so it can still be read. Never fix widths by character count: fonts differ across computers. - 7
Keep text as text. Letters converted to paths (a common export from design tools) cannot be selected, searched, translated or read by a screen reader. Use real
<text>for anything a person must read. Paths are fine for logos and icons. Fonts come from the page's CSS when the SVG is inline in the page; an SVG loaded as an image (<img src="x.svg">) cannot use the page's fonts or CSS at all.
Explain it without notes
Which wins, a fill attribute or a CSS rule? What beats a CSS class?
Where is the baseline of an SVG text, and what does text-anchor="end" do?
How wide is a stroke on a rectangle, relative to its edge?
What happens when a label is longer than its slot, and how do you handle it?
Practice
Make a <text> label that reads Sat centred under a bar whose left edge is at x = 280 and width 40, 14 units below a baseline of y = 180. Write the tag.
A rectangle uses fill="currentColor". Write the CSS so that it is dark in light mode and light in dark mode, using the class dark on body.
Trade-offs
- ↔
Colours in attributes are easy to read in one file but cannot be themed. Colours in CSS cost a stylesheet and can change with the theme, the hover and the screen.
- ↔
Shortening long labels keeps the chart tidy but hides words. Keep the full text in a
<title>or a tooltip so nothing is lost.
Done when you can
I know the order of power: attribute, then CSS class, then inline style.
I can use
currentColorto follow a theme.I can place text by baseline and anchor, and make a two-line label with tspan.
I can measure a label and shorten it with a title that keeps the full text.
I know why text should stay text, not paths.