CSS mask-image
A utility class mounts the SVG via mask-image and recolors it with currentColor. optical-center: auto on the same rule rewrites the underlying SVG and inlines it as a data: URI.
When to use it
Pick this when the icon is delivered as a CSS mask — typically
when you want every icon to inherit currentColor so
the same class can render in any text color without per-icon
variants.
- shadcn-style icon primitives.
- Tailwind
mask-utilitystacks. - Anywhere an icon ships as a one-color mask on top of a colored background.
How to write it
Mount the SVG via mask, paint it with
background, and add
optical-center: auto to the same rule. The
rule's element is the icon — there's no wrapper here.
.icon-play {
display: inline-block;
width: 24px;
height: 24px;
background: currentColor;
mask: url('lucide-static/icons/play.svg') center / contain no-repeat;
optical-center: auto;
} <button>
<span className="icon-play" />
</button> What gets emitted
The PostCSS plugin reads each url('…svg') in the rule,
rasterizes the file, runs the centering pipeline, rewrites the
SVG's viewBox, and inlines the result as a
data:image/svg+xml,… URI in place of the original URL.
.icon-play {
display: inline-block;
width: 24px;
height: 24px;
background: currentColor;
mask: url('data:image/svg+xml;utf8,…rewritten…') center / contain no-repeat;
--optical-center: auto;
} Two things to notice:
- The shift lives in the mask asset. The data URI
contains an SVG with its
viewBoxalready corrected. The browser sees a flat, pre-shifted icon and does no runtime work. - No positioning was emitted. Unlike the wrapper
patterns, this rule doesn't pick up
display: flexortranslate— the perceptual shift is internal to the asset, so the element's position is entirely the consumer's layout concern. Drop it in a flex item, a grid cell, a button, anywhere.
URL resolution
The plugin resolves URLs in this order:
data:URIs and absolutehttp(s):URLs are skipped.- Any matching
aliasesprefix you configured gets expanded. - Absolute paths and
.//../paths resolve against the CSS file's directory. -
Bare specifiers like
lucide-static/icons/play.svggo through Node's module resolution against the CSS file's directory. That makes installed npm icon packages work without alias config.
import opticalCenter from 'optical-center/postcss';
export default {
plugins: [
opticalCenter({
aliases: {
'@icons': '/abs/path/to/icons',
},
}),
],
}; Centering is the consumer's job
Because this rule doesn't emit positioning, the element it
targets needs to be centered however the surrounding layout
normally centers things. Inside a flex parent, that's
justify-content + align-items on the
parent, or margin: auto on the child — both work
because the mask's viewBox already carries the shift.