SVG foreignObject: put HTML inside an SVG
The SVG foreignObject element holds HTML inside a drawing: wrapped text, form fields, anything CSS can lay out. How to size it, the xmlns rule and its limits.
Published
<foreignObject> is a rectangle inside an SVG that holds content from another language, which in a browser nearly always means HTML. It is the way to get things SVG cannot do itself: text that wraps, form fields, a block laid out with CSS. Give it x, y, width and height, and put the HTML inside.
<svg viewBox="0 0 200 100" width="400">
<rect width="200" height="100" fill="gold"/>
<foreignObject x="10" y="10" width="180" height="80">
<p style="margin: 0; font: 14px sans-serif">
This paragraph is HTML. It wraps to the width of the box, which SVG text cannot do.
</p>
</foreignObject>
</svg>
The paragraph is laid out by the browser’s HTML engine inside a box 180 units wide, and breaks into lines to fit.
The attributes
| Attribute | Meaning |
|---|---|
x, y | The top-left corner of the box, in the SVG’s coordinates. Default 0 |
width, height | The size of the box. Set both: without them Chrome gives the box no size and shows nothing |
The name is written foreignObject, with a capital O. In a separate .svg file, or when the element is created from script, the lower-case spelling does not work.
In a separate .svg file: add the HTML namespace
Inside an HTML page the browser knows that a <p> or a <div> within a <foreignObject> is HTML. In a stand-alone .svg file it does not, because the file is XML and every element belongs to the SVG namespace unless told otherwise. Put xmlns="http://www.w3.org/1999/xhtml" on the outermost HTML element:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect width="200" height="100" fill="gold"/>
<foreignObject x="10" y="10" width="180" height="80">
<div xmlns="http://www.w3.org/1999/xhtml" style="font: 14px sans-serif">
This text wraps inside a stand-alone SVG file.
</div>
</foreignObject>
</svg>
Without that attribute the file is still valid XML and opens without an error, but the box is empty. Because a file is XML, the HTML in it must also be well formed: every tag closed, <br/> and not <br>. SVG xmlns explains namespaces.
The box scales with the drawing
The box is part of the SVG, so everything in it is scaled, moved and rotated along with the shapes. In the first example the viewBox is 200 units wide and the SVG is shown 400 pixels wide, so one unit is two pixels and the text set at 14px is drawn 28 pixels high.
That is usually what a diagram needs: labels grow with the picture. It surprises people who expect CSS pixels to stay CSS pixels. If the HTML looks tiny or huge, compare the viewBox with the size on the page: SVG viewBox explained has the arithmetic.
A transform works too:
<svg viewBox="0 0 200 100" width="400">
<foreignObject x="60" y="30" width="80" height="40" transform="rotate(-10 100 50)">
<button style="width: 100%; height: 100%">A real button</button>
</foreignObject>
</svg>
The button is tilted ten degrees and can still be clicked.
The box does not grow
A <foreignObject> has the size you give it and no more. It does not stretch to fit its contents, and Chrome cuts off whatever overflows. Two ways to cope:
- Make the box generous and let the HTML sit in part of it. An empty part of the box is invisible.
- Measure the content with script and set the height to match:
const box = document.querySelector('foreignObject');
box.setAttribute('height', box.firstElementChild.scrollHeight);
To centre text in the box both ways, make the HTML inside a flex container that fills it:
<svg viewBox="0 0 200 100" width="400">
<rect width="200" height="100" fill="gold"/>
<foreignObject width="200" height="100">
<div style="height: 100%; display: flex; align-items: center; justify-content: center; text-align: center; font: 14px sans-serif">
Centred across and down, on as many lines as it takes
</div>
</foreignObject>
</svg>
Styling and scripting
In an inline SVG the HTML inside a <foreignObject> is ordinary page content. The page’s stylesheet applies to it, it inherits the page’s font, and scripts can find it with querySelector like anything else. Links, buttons and form fields work.
To create one from JavaScript, the <foreignObject> itself needs the SVG namespace and the HTML inside it does not:
const svg = document.querySelector('svg');
const box = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
box.setAttribute('width', 180);
box.setAttribute('height', 80);
const p = document.createElement('p');
p.textContent = 'Made by script';
box.append(p);
svg.append(box);
SVG and JavaScript covers createElementNS.
Where it works and where it does not
| Place | Result |
|---|---|
Inline <svg> in a web page | Works |
<object> or <iframe> | Works. The file needs the xmlns on its HTML |
<img> or a CSS background | Chrome draws the HTML, with the limits of any SVG image: no scripts, no clicks, nothing loaded from outside the file. Test the other browsers you support |
| Vector editors, cutting and plotting software, converters | Do not count on it. Expect an empty box or nothing |
The last row is the one that matters for files that leave the browser. The SVG specification does not require a program to draw HTML inside a <foreignObject>, and it says so. A file that must open in a drawing program, on a cutting machine or in a document needs real SVG <text>: see SVG text for laying out lines by hand, or convert the text to outlines.
When to use something else
- A label or a heading on one line:
<text>. It is real SVG and opens everywhere. - Wrapped text in a file for other programs:
<text>with a<tspan>for each line. - A tooltip or a caption over a chart: an HTML element positioned over the SVG is simpler and is not scaled or clipped by the drawing. See SVG tooltip.
- A picture: the SVG
<image>element, not an HTML<img>in a<foreignObject>.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| Nothing is shown | No width or height | Set both |
| Nothing is shown in a .svg file | The HTML has no xmlns="http://www.w3.org/1999/xhtml" | Add it to the outermost HTML element |
| Nothing is shown when made by script | Created with createElement, or spelt foreignobject | createElementNS with the SVG namespace and a capital O |
| The file will not open: XML error | HTML that is not well formed, such as <br> | Close every tag: <br/> |
| The content is cut off | It is larger than the box | Enlarge the box, or set its height from script |
| The text is far too big or too small | It is scaled with the viewBox | Set the font size in the drawing’s units |
| It shows in the browser and not in another program | The program does not support the element | Use <text>, or convert to outlines |
| SVG shapes inside the box do not show | Only HTML is drawn there | Wrap them in their own <svg> element inside the box |
Questions
Does foreignObject work in all browsers? Chrome, Edge, Firefox and Safari have all supported the element for many years. The examples on this page were tested in Chrome.
Can it hold a video, a canvas or an iframe? The specification sets no limit on which HTML elements go inside, and a <canvas> drew correctly in Chrome when tested for this guide. Text and simple form controls are the well-trodden uses. Test anything heavier in each browser you support.
Is it safe to accept in uploaded files? Treat it like HTML, because that is what it carries. A site that takes SVG uploads from strangers should strip it along with scripts.
Does optimising a file change it? The SVG optimizer leaves everything inside a <foreignObject> alone, since white space in HTML can matter.