Documentation
optical-center bakes perceptual centering into the elements that hold your icons. One declaration, two surfaces, five patterns — always on a wrapper, never on the icon itself.
Two layers
optical-center meets your project at two levels. Most people only ever touch the first.
- Automatic — the icons you import. Add the Vite plugin and the icon libraries you already use (Iconify, your own SVG files, unplugin-icons, a hand-rolled icon map) come out optically centered with no markup at all. The build finds the icon SVG by shape and bakes the shift into the asset. This is the path most projects want — Any icon library covers it case by case.
- The directive — when you want explicit control.
For icons the build can't reach (a component library's render
output, a runtime-fetched glyph) or when you'd rather centre the
slot than the asset, you write
optical-center: autoon the container. That declaration, and the five patterns that apply it, are the rest of this page.
The two compose freely — automatic correction for your imported icons, the directive wherever you reach for it.
The rule
The directive lives on the container that wraps the
icon, never on the icon itself. The icon comes in unchanged — your
imported <Play />, your designer's
<svg>, your utility's mask: url(…svg).
The container picks up optical-center: auto and the
build step does the rest.
Conceptually the directive replaces flex centering:
justify-content: center and
align-items: center tell the browser where the
bounding box's middle is; optical-center: auto tells
it where the eye actually wants the icon to land. Same surface,
same property tier, more accurate result.
Two surfaces, one declaration
optical-center: auto can arrive in two places — they
mean the exact same thing.
In CSS, on a rule that targets the container
.badge {
optical-center: auto;
} As an attribute on the container, in HTML or JSX
<div optical-center="auto">
<Play />
</div>
Both forms compile to the same emitted CSS: display: flex
on the wrapper, margin: auto on the icon child, plus a
translate that carries the per-icon perceptual shift.
Centering happens via the well-known flex auto-margin trick — no
justify-content, no align-items, no
position: absolute on the child.
The five patterns
Pick whichever surface fits how your project already declares icons. All five compose; you can mix CSS-class wrappers in one component and Tailwind utilities in another without conflict.
- 01 CSS class Hand-written CSS rule on the wrapper. The PostCSS plugin scans your JSX, links the class to the icon, emits centering + translate. Canonical · use when in doubt
- 02 Tailwind utility Drop the optical-center utility on any wrapper. Same end-state as the CSS-class pattern, registered as a Tailwind component. Tailwind projects
- 03 JSX attribute Inline directive on the wrapper, handled by Babel. No CSS file needed — the plugin injects display:flex and translate as inline styles. One-off wrappers, no CSS layer
- 04 Inline SVG Hand-written <svg> inside the wrapper. Babel rewrites its viewBox at compile time so the shift lives inside the asset, not on the element. Designer hand-offs, hand-rolled glyphs
- 05 CSS mask-image The element IS the icon — mounted via mask-image and recolored by currentColor. PostCSS rewrites the SVG and inlines a data: URI. Utility-icon CSS, currentColor recoloring