JSX attribute

Inline directive on a wrapper. Babel handles the whole flow — no CSS file, no Tailwind plugin, no PostCSS directive.

When to use it

Reach for this when you have a one-off wrapper and don't want a CSS file just for it. The directive arrives in JSX directly; the Babel plugin owns the entire transform.

How to write it

Put optical-center="auto" on the wrapper element. The icon child can be anything Babel can statically resolve to an SVG — an imported icon component or a hand-written <svg> subtree.

Button.tsx tsx
import { Play } from 'lucide-react';

export function PlayButton() {
  return (
    <button>
      <div optical-center="auto">
        <Play />
      </div>
    </button>
  );
}

The boolean shorthand optical-center and the camelCase form opticalCenter="auto" are both accepted — identical semantics either way.

What gets emitted

The Babel plugin reads the wrapper, resolves <Play /> against the file's import to find its source SVG, runs the centering pipeline once, and injects two inline styles:

dist/Button-*.js jsx
<div
  style={{ display: 'flex' }}
  data-optical-center=""
>
  <Play style={{ margin: 'auto', translate: '4.3365% 2.604%' }} />
</div>

Same conceptual output as the CSS class pattern — display: flex on the wrapper, margin: auto + translate on the icon. Carried as inline React style objects instead of a CSS rule.

Accepted shapes

The plugin understands three shapes:

Wrapper around an imported icon component

component.tsx tsx
<div optical-center="auto">
  <Play />
</div>

Wrapper around a hand-written inline SVG

Same syntax. Babel rewrites the SVG's viewBox in place instead of emitting a translate — the perceptual shift goes into the asset. See Inline SVG for the requirements that come with this form.

component.tsx tsx
<div optical-center="auto">
  <svg viewBox="0 0 24 24">
    <polygon points="6 3 20 12 6 21 6 3" />
  </svg>
</div>

Marker on the SVG itself (lower-level shortcut)

Skipping the wrapper works too — the SVG's viewBox gets rewritten directly. There's no inline-style centering in this form; you're on your own to position the SVG. Prefer one of the wrapper forms above unless you have a reason.

component.tsx tsx
<svg optical-center="auto" viewBox="0 0 24 24">
  <polygon points="6 3 20 12 6 21 6 3" />
</svg>

Limits

  • The wrapper can't use spread attributes (<div optical-center="auto" {...rest}>) — the spread might inject a competing directive at runtime, so the plugin bails out with OPTICAL_SPREAD_PROPS.
  • The wrapper can't already have a style attribute the plugin would have to merge with. Leave the wrapper styleless and put per-element styling on inner elements.
  • The icon child must be statically identifiable — an imported icon component the plugin tracks, or a static <svg> subtree. Dynamic children (<div optical-center="auto">{icon}</div>) bail out with OPTICAL_DYNAMIC_SVG.

See warning codes for the full list of bail-out reasons and how to reach them in CI.