@ledge-inc/optical-center
v0.2.0
Published
Build-time optical centering for SVG, Lucide, and Phosphor icons in Next.js.
Maintainers
Readme
@ledge-inc/optical-center
Build-time optical centering for SVG, Lucide, and Phosphor icons in Next.js 16. The compiler measures a selected icon, then bakes its perceptual correction into the generated JSX. Nothing from this package runs in the browser.
This project is a focused adaptation of Grkmyldz148/optical-center.
Install
npm install @ledge-inc/optical-centerInstall the matching source-asset package for each component library you use:
npm install lucide-react lucide-static
npm install @phosphor-icons/react @phosphor-icons/coreNode 20.9 or newer and Next.js 16 are required.
Configure Next.js
// next.config.ts
import type { NextConfig } from 'next';
import withOpticalCenter from '@ledge-inc/optical-center';
const nextConfig: NextConfig = {
reactStrictMode: true,
};
export default withOpticalCenter(nextConfig, {
emitMetadata: process.env.NODE_ENV !== 'production',
});The wrapper adds a pre-loader to both Webpack and Turbopack. It keeps Next's SWC and React Compiler pipeline intact and composes with existing config hooks.
Select an icon
Place exactly one supported icon directly inside a marked wrapper:
import { Play } from 'lucide-react';
export function PlayButtonIcon() {
return (
<span data-optical-center="auto">
<Play />
</span>
);
}The build output receives geometric layout and the precomputed optical shift:
<span data-optical-center="" style={{ display: 'flex' }}>
<Play style={{ margin: 'auto', translate: '…% …%' }} />
</span>Unmarked icons are never analyzed or modified.
Inline SVG
Inline, static SVG markup is corrected by rewriting its viewBox:
<span data-optical-center="auto">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M8 5v14l11-7z" />
</svg>
</span>Dynamic SVG paths, spreads, and expression children are left unchanged with a build warning because their rendered geometry cannot be known statically.
Local SVG files with SVGR
The compiler follows a local .svg component import back to its source file:
import Play from './play.svg';
<span data-optical-center="auto">
<Play />
</span>Configure SVGR for both bundlers; withOpticalCenter preserves these rules:
import type { NextConfig } from 'next';
import withOpticalCenter from '@ledge-inc/optical-center';
const nextConfig: NextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
use: ['@svgr/webpack'],
});
return config;
},
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
};
export default withOpticalCenter(nextConfig);Default imports and Create React App-style
{ ReactComponent as Icon } imports are recognized.
Phosphor
All six static Phosphor weights are supported in client and server imports:
import { PlayIcon } from '@phosphor-icons/react/ssr';
<span data-optical-center="auto">
<PlayIcon weight="duotone" mirrored={false} />
</span>Supported weights are thin, light, regular, bold, fill, and
duotone. A missing weight means regular. Literal mirrored values invert
the horizontal correction. Dynamic weight or mirrored expressions are
left unchanged with a warning.
Style and failure contract
The compiler adds display: flex to the wrapper and margin: auto to the
icon. Imported components also receive translate. Existing static style
objects are merged without overwriting values. Dynamic style expressions,
spreads, conflicting layout values, multiple children, malformed SVGs, and
unresolvable source assets produce a warning and leave the marked subtree
unchanged.
Options
interface OpticalCenterOptions {
emitMetadata?: boolean;
cacheDir?: string;
disableCache?: boolean;
}emitMetadataadds the computed offset for inspection. It defaults totrueoutside production andfalsein production.cacheDiroverrides.next/cache/optical-center.disableCachedisables disk and in-process caching, primarily for tests.
Cache entries include the SVG bytes, icon variant, and algorithm version.
Migrating from upstream optical-center
- Replace
optical-center/nextwith the package root import shown above. - Replace
optical-centeroropticalCenterdirectives withdata-optical-center="auto"on a wrapper. - Remove Vite, Astro, PostCSS, Tailwind, CLI, and Iconify configuration; those surfaces are intentionally not part of this package.
- The browser-safe model and raw SVG transformation helpers are internal and are not supported public APIs.
License
MIT. The retained model and core implementation are attributed to the original optical-center project and contributors.
