Skip to content

fix: scope per-instance customize to unique CSS selector#483

Open
mukundshah wants to merge 1 commit intonuxt:mainfrom
mukundshah:main
Open

fix: scope per-instance customize to unique CSS selector#483
mukundshah wants to merge 1 commit intonuxt:mainfrom
mukundshah:main

Conversation

@mukundshah
Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves #482

📚 Description

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.

When customize is a function, a hash of fn.toString() is now appended to the CSS class name so each distinct customization gets its own selector and CSS rule. The SSR dedup key is updated to match.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

This change adds per-instance CSS selector hashing to the CSS mode component when a customize function is provided. When props.customize is a function, the CSS class name is appended with --customized-${hash(customizeFnSource)} to create unique selectors for distinct customizations. The SSR CSS deduplication mechanism was updated to key by the computed cssClass.value (which includes the customize hash) instead of props.name, enabling customized and non-customized variants of the same icon to each maintain separate CSS rules.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding per-instance CSS selector hashing for the customize prop to scope it to unique selectors.
Description check ✅ Passed The description clearly explains the problem, solution, and implementation details related to the CSS customize prop scoping issue.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #482: per-instance customize hashing, unique CSS selectors, and updated SSR deduplication keying.
Out of Scope Changes check ✅ Passed All changes in src/runtime/components/css.ts are directly scoped to resolving issue #482 and implementing the specified customize prop fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/runtime/components/css.ts (1)

149-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

watch only observes props.name, leaving props.customize changes un-serviced

After this PR, cssClass.value (and therefore selector.value) depends on both props.name and props.customize. The watch source is still () => props.name, so if the customize prop is updated while name stays the same:

  1. cssClass.value gets a new --customized-<hash> suffix.
  2. The rendered <span> immediately receives the new class (reactive binding at line 214).
  3. The watch callback never fires — no mountCSS call is made for the new selector.
  4. The icon renders with the new class but without a matching CSS rule, so the customization is silently dropped.
🐛 Proposed fix — watch the computed cssClass instead of the raw prop
      watch(
-       () => props.name,
+       cssClass,
        () => {
          if (selectors.has(selector.value)) {
            return
          }
          const data = getIcon(props.name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/css.ts` around lines 149 - 169, The watch currently
observes only props.name so updates to props.customize (which change
cssClass.value and selector.value) won’t trigger mounting CSS; update the watch
source to observe the computed cssClass (or include props.customize) instead of
just () => props.name so that when cssClass/selector changes the callback runs
and calls mountCSS/loadIcon for the new selector; adjust the watch invocation
that references watch(() => props.name, ...) to reference the computed cssClass
(or a function that returns `${props.name}-${props.customize}`) and keep the
existing mountCSS/loadIcon logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/css.ts`:
- Around line 85-94: The computed cssClass currently hashes
props.customize.toString(), which can differ between SSR and client builds and
cause hydration mismatches; add a new prop (e.g., customizeKey) that users can
set to a stable string and change the cssClass computation (the computed named
cssClass) to use props.customizeKey as the hash source when present, otherwise
fall back to props.customize.toString(); ensure the hash call (hash(...)) and
the selector prefix (options.cssSelectorPrefix + props.name) remain unchanged
and document the new customizeKey prop so consumers know to provide a stable key
for SSR.

---

Outside diff comments:
In `@src/runtime/components/css.ts`:
- Around line 149-169: The watch currently observes only props.name so updates
to props.customize (which change cssClass.value and selector.value) won’t
trigger mounting CSS; update the watch source to observe the computed cssClass
(or include props.customize) instead of just () => props.name so that when
cssClass/selector changes the callback runs and calls mountCSS/loadIcon for the
new selector; adjust the watch invocation that references watch(() =>
props.name, ...) to reference the computed cssClass (or a function that returns
`${props.name}-${props.customize}`) and keep the existing mountCSS/loadIcon
logic intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de24b735-fb17-454a-bb25-6b47e5b171b0

📥 Commits

Reviewing files that changed from the base of the PR and between 3d72f09 and a8d04bc.

📒 Files selected for processing (1)
  • src/runtime/components/css.ts

Comment on lines +85 to +94
const cssClass = computed(() => {
if (!props.name) return ''
const base = options.cssSelectorPrefix + props.name
// When a per-instance customize function is provided, append a hash of its source
// so that distinct customizations get their own selector and CSS rule.
if (typeof props.customize === 'function') {
return base + '--customized-' + hash(props.customize.toString())
}
return base
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

fn.toString() may differ between SSR and client bundles, causing hydration mismatches in production

In a typical Nuxt production build, the client bundle is minified/mangled while the SSR bundle is not (or is processed with different transforms). A user-supplied customize arrow function such as:

(attr) => { attr.fill = 'currentColor'; return attr }

could serialize differently in each environment:

  • SSR: attr => { attr.fill = 'currentColor'; return attr; }
  • Client (minified): a=>(a.fill="currentColor",a)

These produce distinct hashes, so the SSR-rendered <span> class (e.g. …--customized-abc) and the class the client computes on hydration (e.g. …--customized-xyz) will differ. Vue will emit a hydration mismatch warning, and the icon may be unstyled during the initial client paint until mountCSS runs for the client-side hash.

A pragmatic mitigation would be to expose a customize-key prop that users can set to a stable string, and use that as the hash source when present, falling back to fn.toString() only for client-only rendering:

// user-visible stable key avoids serialize-then-hash fragility
if (typeof props.customize === 'function') {
  const source = props.customizeKey ?? props.customize.toString()
  return base + '--customized-' + hash(source)
}

At minimum, the limitation (hash instability in production SSR builds) should be documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/css.ts` around lines 85 - 94, The computed cssClass
currently hashes props.customize.toString(), which can differ between SSR and
client builds and cause hydration mismatches; add a new prop (e.g.,
customizeKey) that users can set to a stable string and change the cssClass
computation (the computed named cssClass) to use props.customizeKey as the hash
source when present, otherwise fall back to props.customize.toString(); ensure
the hash call (hash(...)) and the selector prefix (options.cssSelectorPrefix +
props.name) remain unchanged and document the new customizeKey prop so consumers
know to provide a stable key for SSR.

Copy link
Copy Markdown
Member

atinux commented May 6, 2026

I think coderabbit comment is good here, also do you have an example of using cutomize with css that works?

@mukundshah
Copy link
Copy Markdown
Contributor Author

Hi @atinux

Here's the repro: https://stackblitz.com/edit/github-fheyzs74

The fix is installed via the patch file. You can revert the patch to reproduce the issue if needed. I’ve also added a screenshot for reference.

Regarding the case pointed out by CodeRabbit, we haven’t observed any hydration issues so far. This fix is already running in production without any problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] customize prop has no effect in CSS mode when the same icon is used elsewhere without it

2 participants