Skip to content

Nested components utilizing v-bind() in CSS improperly reference the CSS variable name of the parent node, causing undesired property inheritance in the child #12434

Open
@adamdehaven

Description

@adamdehaven

Vue version

3.5.13

Link to minimal reproduction

Reproduction playground

Steps to reproduce

Block One

  1. In App.vue we set the backgroundColor prop to a value of lightgray.

  2. Inspect the rendered HTML of "Block One" and notice the value of the background-color prop is bound to the root div element via an inline CSS Custom Property, prefixed by a generated hash, e.g. style="--a4f2eed6-backgroundColor: lightgray;"

  3. Notice the hash is also bound to the data-v-a4f2eed6 attribute (for scoped styles) as well.

  4. Inspect the Styles panel in the browser, and notice the .block class for "Block One" refers to the same CSS Custom Property for the background-color property (prefixed in this example with --a4f2eed6-)

    block one background-color styles

Block Two

  1. In App.vue we do not set the backgroundColor prop for Block Two in the component props, meaning it utilizes the prop default value of null (to prevent the property from being bound to the component since it is not desired for this instance)
  2. Notice the hash (for scoped styles) of Block Two is the same as Block One (i.e. data-v-a4f2eed6).
  3. Inspect the Styles panel in the browser, and notice the .block class for "Block Two" refers to the same CSS Custom Property for the background-color property (prefixed in this example with --a4f2eed6-), rather than generating its own hash to attempt to use for the property.

Block Three

  1. Since the CSS custom property is not defined on a parent node of Block Three, there is no issue in its display.

What is expected?

"Block Two" (as referenced in the Steps to reproduce) should generate its own CSS custom property hash prefix (likely corresponding to the scoped styles hash as well) that is then utilized inside its style rule.

<div data-v-a4f2eed6="" id="v-0" class="block" style="--a4f2eed6-backgroundColor: lightgray;">
  <div>Block One</div>
  <div data-v-a4f2eed6="" id="v-1" class="block">
    <div>Block Two</div>
  </div>
</div>

I would expect the generated CSS in the scenario described to evaluate to this:

/** Block One */
.block {
  &[data-v-a4f2eed6] {
    background-color: var(--a4f2eed6-backgroundColor);
  }
}

/** Block Two */
.block {
  &[data-v-a4f2eed6] {
    background-color: var(--DIFFERENT_HASH-backgroundColor);
  }
}

What is actually happening?

Since instances of the same component share the same generated hash for both the scoped styles as well as generated CSS custom properties, the CSS background-color cannot be explicitly "unset" by providing a null prop value for a secondary, nested instance of the same component.

The result of this means that in order for the child component to not reference the CSS Custom Property of its parent, you have to explicitly set the default prop value that is mapped to v-bind to something that essentially "unsets" the value that may be present from the parent.

Using the example from the reproduction, to solve the issue, I would have to change the defineProps implementation to set the backgroundColor prop to initial (i.e. a reset), or alternatively pass a value of something like transparent which isn't ideal.

const { backgroundColor = 'initial' } = defineProps<{ backgroundColor?: string }>()

This will then set the value of the same CSS Custom Property, on the child node, to initial

<div data-v-a4f2eed6="" data-v-7ba5bd90="" id="v-0" class="block" style="--a4f2eed6-backgroundColor: lightgray;">
  <div data-v-7ba5bd90="">Block One</div>
  <div data-v-a4f2eed6="" data-v-7ba5bd90="" id="v-1" class="block" style="--a4f2eed6-backgroundColor: initial;">
    <div data-v-7ba5bd90="">Block Two</div>
  </div>
</div>

System Info

No response

Any additional comments?

Generating a separate hash for each instance of a component (rather than reusing the same one for each one) would obviously lead to a lot of bloat in the injected DOM styles, especially for instances where this behavior doesn't exist.

I'm unsure if having to explicitly provide a "reset" value makes sense either, as it will bloat the inline style rules on every top-level component DOM element to set and/or reset the value.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions