Problem
In CSS mode, the per-instance customize prop was not scoped to its instance ; depending on render order, either all instances of the same icon got customized or none did.
Example:
<Icon name="lucide:wallet" :customize="customize" />
<Icon name="lucide:wallet" />
Root cause
CSS mode works by injecting a <style> tag with a CSS rule keyed on the icon's class name (e.g. icon-lucide:wallet). To avoid duplicate rules, a Set (client) and a Map (SSR) are used to track which selectors have already been injected.
Both were keyed only on the icon name, with no awareness of the customize prop:
- Client:
selectors.has(selector.value) — whichever instance mounts first wins; the other's mountCSS is silently skipped. Since cssSelectors is a module-level Set, it persists across client-side navigation — whichever page or component first injects the rule for a given icon locks it in for the entire session.
- SSR:
ssrCSS.has(props.name) — same issue; first write wins.
This means two instances of the same icon sharing the same selector will always render identically, regardless of their individual customize props.
Fix
Unique selector per unique customize
When props.customize is a function, append a short hash to the CSS class name so that each distinct customization gets its own selector and CSS rule:
icon-lucide:wallet ← no customize / global customize
icon-lucide:wallet--customized-<hash> ← per-instance customize
if (typeof props.customize === 'function') {
return base + '--customized-' + hash(props.customize.toString())
}
Instances with identical customizations still share one CSS rule (deduplication preserved).
The hash is computed from fn.toString() using ohash (already a dependency).
SSR dedup key
Changed the SSR ssrCSS Map key from props.name to cssClass.value so customized and non-customized variants of the same icon each get their own CSS entry in the SSR-rendered <style> tag.
Problem
In CSS mode, the per-instance
customizeprop was not scoped to its instance ; depending on render order, either all instances of the same icon got customized or none did.Example:
Root cause
CSS mode works by injecting a
<style>tag with a CSS rule keyed on the icon's class name (e.g.icon-lucide:wallet). To avoid duplicate rules, aSet(client) and aMap(SSR) are used to track which selectors have already been injected.Both were keyed only on the icon name, with no awareness of the
customizeprop:selectors.has(selector.value)— whichever instance mounts first wins; the other'smountCSSis silently skipped. SincecssSelectorsis a module-levelSet, it persists across client-side navigation — whichever page or component first injects the rule for a given icon locks it in for the entire session.ssrCSS.has(props.name)— same issue; first write wins.This means two instances of the same icon sharing the same selector will always render identically, regardless of their individual
customizeprops.Fix
Unique selector per unique
customizeWhen
props.customizeis a function, append a short hash to the CSS class name so that each distinct customization gets its own selector and CSS rule:Instances with identical customizations still share one CSS rule (deduplication preserved).
The hash is computed from
fn.toString()usingohash(already a dependency).SSR dedup key
Changed the SSR
ssrCSSMap key fromprops.nametocssClass.valueso customized and non-customized variants of the same icon each get their own CSS entry in the SSR-rendered<style>tag.