SVG, XHTML and XML in the browser
Inline SVG as real XML, XHTML strictness and the XML parsing mode, foreignObject for mixing vocabularies, and the namespace serialisation pitfalls that break rendering.
SVG in a page is XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
<title>Request latency by hour</title>
<defs>
<linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#4c8bf5"/>
<stop offset="1" stop-color="#4c8bf5" stop-opacity="0"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="200" height="80" fill="url(#fade)"/>
<circle cx="40" cy="40" r="6" fill="#fff"/>
<path d="M10 60 L60 30 L110 45 L160 15" fill="none" stroke="#fff" stroke-width="2"/>
</svg>- An inline SVG element inherits the surrounding document's namespace rules, and a standalone SVG file declares its own.
- Attributes in SVG are case-sensitive:
viewBoxis notviewbox, andpreserveAspectRatiois camel-cased. - Styling can come from CSS, but presentation attributes cannot easily be overridden by a class with equal specificity.
foreignObjectembeds HTML content inside SVG, which needs its own namespace declaration on the child element.
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="60">
<foreignObject x="0" y="0" width="240" height="60">
<div xmlns="http://www.w3.org/1999/xhtml" style="font: 14px sans-serif">
Regular HTML inside SVG, with a <b>bold</b> word.
</div>
</foreignObject>
</svg>Serving a page as XML
GET /page HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/xhtml+xml; charset=utf-8
<!-- parsed in XML mode: any error is a fatal parse error -->| Mistake | HTML mode | XHTML or XML mode |
|---|---|---|
Unclosed <p> | Recovered by the parser | Fatal parse error, page fails |
| Unescaped ampersand | Recovered | Fatal parse error |
| Attribute without quotes | Accepted | Fatal parse error |
Case mismatch <DIV> | Ignored | A different element in the XML model |
| Missing namespace | Elements lose styling | Elements are in no namespace, rendering fails |
| Invalid character | Substituted | Fatal parse error |
The browser shows an error page rather than a partially rendered document, which is why most sites stay on text/html and treat well-formedness as a coding standard rather than a delivery mode.
Namespaces and serialisation pitfalls
// creating an element in the SVG namespace, not the HTML one
const NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(NS, "svg");
const circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", "40"); // plain attributes need no namespace
circle.setAttributeNS(NS, "xlink:href", "#icon"); // namespaced attribute needs the qualified form
svg.appendChild(circle);
document.body.appendChild(svg);
// element.innerHTML on an SVG element parses in HTML mode and can lose the namespace
const text = new XMLSerializer().serializeToString(svg);- Create SVG nodes with
createElementNS, nevercreateElement. - Set namespaced attributes with
setAttributeNS; a plain set often produces an attribute the renderer ignores. - Serialising a subtree can drop a namespace declaration if the parent supplied it, producing a fragment that fails to render when reinserted.
- Cloning a node across documents imports nothing of the namespace map, so declare the namespace on the copied root.
- Data inserted as markup is script-capable. Put untrusted text in as a text node so it is escaped automatically.
💡
An inline SVG diagram is code, not an image. Content inserted into it from a user, a log file or a third-party API must be treated as untrusted and inserted as text, never as markup, or the drawing surface becomes an injection point.
FAQ
Should I serve XHTML?
Rarely.
text/html with well-formed markup gives you the interoperability without the fatal-error behaviour. Use application/xhtml+xml only when you specifically need XML processing in the browser.Why does my inline SVG render as a blank box?
Usually a missing
xmlns on the root, or the element was created with createElement so it landed in the HTML namespace and has no SVG rendering.Related
JSON or XML today: migration and coexistence Transforming XML with XSLT templates
Last refreshed 2026-09-18.