Skip to content

HTML5 Boolean Attributes #1972

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

Open
dacohen opened this issue Sep 15, 2022 · 6 comments
Open

HTML5 Boolean Attributes #1972

dacohen opened this issue Sep 15, 2022 · 6 comments
Labels
enhancement New feature or request

Comments

@dacohen
Copy link

dacohen commented Sep 15, 2022

What problem does this feature solve?

According to the HTML5 spec:

The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.

- https://www.w3.org/TR/html5/infrastructure.html#boolean-attributes

For several reasons, especially using Custom Elements / Web Components, it would be helpful to be able to set attributes without corresponding values, in order to comply with the standard. For example, <input :disabled="true"> should generate <input disabled> not <input disabled="true". <input :disabled="false"> should generate <input> not <input disabled="false">.

It seems that <input :disabled="isDisabled ? '' : null"> gets close (correctly handing the false case), but this still generates <input disabled=""> in the true case.

What does the proposed API look like?

This exact issue was discussed back in 2016, for Vue 2 (See vuejs/vue#2169). The decision was to add a .boolean modifier to v-bind.

In the above example, you could then write <input :disabled.boolean="true"> which would generate <input disabled> and <input :disabled.boolean="false"> which would generate <input>.

Has this change, or an equivalent one, made it into Vue 3? If not, and the decision made in 2016 still makes sense, I'm happy to contribute a PR.

@LinusBorg
Copy link
Member

LinusBorg commented Sep 15, 2022

I'll preface this by saying that I'm not opposed to this request per se. However, the problem as you describe it does not exist in Vue 3 anymore for the most part.

Vue 3 already handles boolean attributes as described by the spec correctly, as in: HTML attributes that are defined as boolean attributes in the spec (like input's disabled) will render as you expect them to.

All other attributes (and that includes any custom attributes not defined in the spec) will render boolean values as true/false strings. Strictly speaking, this also is spec-compliant as el.setAttribute('custom-1', true) will result in custom-attribute="true". Only null and undefined are used as indicators to remove an attribute, as we needed something to indicate that intent in a declarative way.

Concerning custom elements, this can be a bit annoying, but if their implementation follows the recommended best practices, they accept attributes as DOM properies as well, so you can use the .prop modifier like this: :custom-2.prop="true" and have the desired result in your web component. I've also seen custom elements accepts 'true'/'false' values for boolean props to ensure better compat.

But there's a catch, namely that the .propmodifier will set it as a dom property and as such, not reflect as an attribute on the element that you could select for in CSS. For this, you would need to do: :custom-3="booleanValue ? '' : null", which is cumbersome.

For this, a .boolean flag would be useful and could be discussed. However this really limits the scope of the request to "Custom attributes that I want to treat like boolean attributes for easier selection in CSS".

Here's a Playground demonstrating all of the above.

@dacohen
Copy link
Author

dacohen commented Sep 15, 2022

Thanks for the overview, that's very helpful. I definitely see what you mean regarding the limited utility of a .boolean modifier.

Perhaps the best course of action, at least in the short term, is to update the documentation for "Vue and Web Components" (https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue) to clarify how the .prop modifier can be used to handle this scenario, as well as the potential pitfalls. I'm happy to take a pass at this, if you think it makes sense.

Thanks again for your help.

@LinusBorg
Copy link
Member

Sure, docs contributions are welcome. I'd suggest opening an issue on docs repo and coordinate with our docs maintainers about it.

@yyx990803 yyx990803 transferred this issue from vuejs/core Sep 27, 2022
@wassim-ben-amor
Copy link

Hello, are there any updates about this issue ?
Today when using <my-custom-element :disabled="false" />, the rendered dom is including disabled="false". Do we need to pass null | undefined instead of false ?

@WickyNilliams
Copy link
Contributor

WickyNilliams commented Dec 18, 2023

i am trying to understand in detail vue's handling of the property/attribute duality when it comes to custom elements and booleans.

the docs say:

When setting props on a custom element, Vue 3 automatically checks DOM-property presence using the in operator and will prefer setting the value as a DOM property if the key is present

so i have a custom element my-element which has a property myBool and a corresponding attribute my-bool. setting one reflects in the other, so that:

myElement.myBool === false
myElement.hasAttribute("my-bool") === false

myElement.myBool = true
myElement.hasAttribute("my-bool") === true

myElement.removeAttribute("my-bool")
myElement.myBool === false

if i render this in vue:

<my-element :my-bool="false" />

the html output is:

<my-element my-bool="false"></my-element>

i'm curious how it ends up setting the attribute in this case, since "myBool" in instanceOfMyElement evaluates to true. i assume vue converts my-bool to camelCase before the in check? if so, how does this end up as the output? i've tried to find the code to step through myself, but i'm struggling to find it in the vue codebase.

example code for such an element
export class MyElement extends HTMLElement {
  #shadow

  static observedAttributes = ["my-bool"]

  get myBool() {
    return this.hasAttribute("my-bool")
  }
  set myBool(value) {
    this.toggleAttribute("my-bool", value)
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    this.#render()
  }

  constructor() {
    super()
    this.#shadow = this.attachShadow({ mode: "open" })
  }

  connectedCallback() {
    this.#render()
  }

  #render() {
    this.#shadow.innerHTML = this.myBool.toString()
  }
}

if (!customElements.get("my-element")) {
  customElements.define("my-element", MyElement)
}

@WickyNilliams
Copy link
Contributor

ok, i've found the code where the check is done: https://github.com/vuejs/core/blob/04d2c05054c26b02fbc1d84839b0ed5cd36455b6/packages/runtime-dom/src/patchProp.ts#L131

stepping through the code, it seems that the property check is done before the camel case conversion. this seems odd, incorrect even, to me? AFAIK no properties in HTML use kebab-case, nor would i expect any custom elements to have kebab case property names, so it's weird to check for this.

if vue defaults to attempting to set properties, but conversely recommends kebab-case for property names in templates, i feel that the in check should be on the camelCase form, and if the check fails, then fallback to the literal name used in the template.

i think many of the issues around booleans would go away if this were changed. but i don't fully understand the implications of such a change! i would be interested to hear others' thoughts on this, particularly @LinusBorg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants