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.
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:
<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
<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.
<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.
<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 withOPTICAL_SPREAD_PROPS. -
The wrapper can't already have a
styleattribute 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 withOPTICAL_DYNAMIC_SVG.
See warning codes for the full list of bail-out reasons and how to reach them in CI.