Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not possible to set attributes (template syntax) on web components/custom elements #13104

Open
bigwigal opened this issue Mar 26, 2025 · 2 comments

Comments

@bigwigal
Copy link

Vue version

^3.0.0

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-dm4frnun?file=src%2FApp.vue,src%2Fmain.ts,src%2Fcomponents%2FAdditionComp.js&terminal=dev

Steps to reproduce

Inspect DOM and observe missing attributes on Vue/Lit instance, also observe incorrect calculation in render of Vue/Lit instance. Vanilla/Lit included for reference.

What is expected?

The component rendered within Vue to be identical to the vanilla/native DOM render.

What is actually happening?

Firstly, I can see that this isn't a bug from Vue's perspective, it's expected behaviour (documented here), but it's something that will undoubtedly cause plenty of bugs. I can also see that this has been reported as a bug before now (at least once here) but issues are now closed/old or appear to be indirectly related, so I'm raising this again to get a fresh perspective.

We're using Lit to create a component library and one of the requirements within the team is for these to be used within Vue. Stencil is mentioned in the linked issue above, which I haven't used, but like Stencil (at least at the time of writing) Lit also doesn't reflect by default. We've had our own discussions around this and are still not in agreement about what the best approach is. My personal opinion has been to only reflect if necessary, e.g. to expose state as hooks for CSS. This seems to be the approach taken in the native DOM where, other than in a handful of cases, markup represents initial state. This is also the view taken by Lit (documented here) and most likely what guided me on my own opinions, that and the fact that it just seems to makes sense to me. To reflect or not to reflect isn't really the issue here though, obviously it gets the attributes in the DOM but it doesn't address the potential issues caused by how values are actually set, i.e. value set via prop of same name (if exists) and everything just ends up defaulting to a string.

The problem with this approach is that it hijacks the setting of props from attributes, so any existing handling that may be in place is sidestepped. This is a particular problem when working with Lit and its pattern for the handling (conversion, validation etc.) of values being set from attribute to prop (documented here), although the same could be true of vanilla WCs and any handling that may be implemented within attributeChangedCallback. In that case, you could argue that handling could be moved to property accessors, which is a fair point although quite opinionated and not useful when using wrappers such as Lit (which at best would result in a load of boilerplate, manually requesting updates etc.).

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
    Memory: 16.03 GB / 31.69 GB
  Binaries:
    Node: 22.14.0 - ~\nvm-symlink\node.EXE
    npm: 10.9.2 - ~\nvm-symlink\npm.CMD
  Browsers:
    Edge: Chromium (131.0.2903.112)
    Internet Explorer: 11.0.19041.4355

Any additional comments?

I know that binding can be used to ensure the correct types (putting the onus on consumers) but any other handling would still be lost. I can also see custom directives have been suggested several times in related issues but I don't think that's practical in this case. We do have tooling for scaffolding projects (all Vite based) so an option to switch this behaviour off would be an acceptable solution, although still not ideal, as we can achieve that in a single place within config, and not on the markup of every custom element that is used.

I have huge respect for Vue (the project and people behind) but I know I will be put off using it due to this issue. I would expect that Vue would look to integrate more seamlessly with these tools, not put up barriers to using them, and as a minimum treat custom elements the same as native elements.

@edison1105
Copy link
Member

workarounds:

  • Both a and b are of type number
<addition-comp :a="2" :b="2"></addition-comp> 
  • Both a and b are of type number and as attrs
<addition-comp :a.attr="2" :b.attr="2"></addition-comp>

@bigwigal
Copy link
Author

@edison1105 thanks for replying so quickly and for the suggested workarounds. The first is a workaround in this case but may be only a partial in others as it still sidesteps any handling tied to attribute change, as well as requiring the consumer to have, at least some, knowledge of component internals (which isn't really the point with WCs). The second is more interesting, wasn't aware of that modifier (documented here). Presumably that approach sets the prop (type number) as well as the attribute. I've checked in the repro and Lit handles the two instances the same (although an additional debounced update is likely requested) with the attribute coming out on top (custom converters run as expected etc.) so that does appear to provide a full workaround.

You probably know what I'm going to say next... it's a lot of additional code just to do something that, on the face of it, is probably the expected behaviour anyway. Reading the docs for .attr I can see now that this is how Vue handles native elements too (prefering prop over attribute), which makes sense, so seems to be just the omission of attributes from the render that differs for custom elements/attributes. It's funny because we actually began writing our comps with data- attributes, later dropping the prefix for brevity, as that is another approach that gets around this issue, and lets be honest is probably the 'right' way to do things (although noone in practice seems to). That's the issue really, there's no hard and fast rules around any of this and the native DOM can seem inconsistent at times so it is a difficult one to work out. My default position with anything, though, is always exactly that, stick as closley to the default behaviour of whatever it is that you're working with as going against it always throws up issues. Thanks again for your help with this.

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

No branches or pull requests

2 participants