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":

PlayBadge.tsx tsx
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.

dist/PlayBadge-*.js jsx
<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:

index.html html
<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:

index.html html
<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.