Inline SVG
Hand-written <svg> inside a container wrapper. Babel rewrites the viewBox at compile time so the perceptual shift lives in the asset, not on the element.
When to use it
Pick this when the icon's markup is authored by hand in the same
file — a designer hand-off, a glyph you sketched in
<svg> directly, an SVG snippet pasted from a
figma export. No icon-component library involved.
How to write it
Wrap the <svg> in a container with
optical-center="auto":
export function PlayBadge() {
return (
<div optical-center="auto">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polygon points="6 3 20 12 6 21 6 3" />
</svg>
</div>
);
} What gets emitted
For a hand-written inline SVG, Babel doesn't need to look up an
asset path or emit a translate — it rewrites the
SVG's viewBox directly. The perceptual shift becomes
part of the asset.
<div style={{ display: 'flex' }} data-optical-center="">
<svg
viewBox="-0.3239 -0.6226 24 24"
data-optical-center=""
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polygon points="6 3 20 12 6 21 6 3" />
</svg>
</div>
Note the difference vs the JSX
attribute pattern with an icon component: there, the child kept
its original SVG and picked up a translate. Here, the
child's viewBox shifted and there's no
translate on the icon. Both produce the same visual
result; the difference is just where the shift lives.
Plain HTML works too
Same pattern outside JSX. Vite's HTML transform (and the Astro
integration's post-build sweep) rewrite the
<svg>'s viewBox in place:
<div optical-center="auto">
<svg viewBox="0 0 24 24" fill="currentColor">
<polygon points="6 3 20 12 6 21 6 3" />
</svg>
</div>
For HTML the marker on the <svg> itself is also
accepted as a lower-level shortcut:
<svg optical-center viewBox="0 0 24 24" fill="currentColor">
<polygon points="6 3 20 12 6 21 6 3" />
</svg> The static-subtree rule
Babel rewrites a hand-written SVG only if the entire subtree is statically known at compile time. That means:
- No spread attributes anywhere in the tree (
<svg {...rest}>). - No
{expression}children inside the SVG. - No fragments around or inside the SVG.
- No conditional rendering that produces different SVG shapes per render.
If the validator can't prove the subtree is static, the plugin bails
out with OPTICAL_DYNAMIC_SVG and leaves the markup
untouched. Duplicate the literal SVG across branches if you need
conditionals — pulling it through any indirection trips the
validator.