Skip to content

withDefaults default value ignored when prop is passed as attribute without value, and result depends on type order #15087

Description

@hendCode

Vue version

3.5.39

Link to minimal reproduction

https://play.vuejs.org/#eNrdUk1zmzAQ/StbLtgzNhzaE8XuGDeH5hBnmhy5UFhspULSSIvtDOa/V4L4g2niGV9z0+57evv2o/EWSgXbGr3IiwkrxTPCeSoA4uWG8WIBZiN3qy3qkssdhD30ZTqFZWYQFjCdXrCT6+ykZ8fhRSEbmlwzRWCQagU8E+tZ6pFJPYuySklN8Gal1LICPwj70Ln2vw85yZCTHDlx2BeZexMrnUtRsnXwYqSwbTfOZOrlslKMo14pYlLY8hF0iMMybtu573Kka5wc8/kG87/v5F/M3uVS71GjQb3F1DthlOk1Ug/fPT3g3r5PYCWLmlv2FfA3Gslr57GnJbUorO0LXuf2VzcVJtbP5m5PKMyxKWfUMduOn3p2QssrrZ/tfg2+df9S0dopnnfw/+UUbDsfHMIMmgbun1YPgSFtPbHydXRJGEPbwoheFdrKDbiHLIe31LbjOHTCN92PU4KjxrMLZuCTlJyY8uEAPnLOlGGmC4gRR99phuHbfUfwx7IxE2AL5lhAybShVNgTMgRKS2UWVnPHaPMTy6zmZEYFlkzgo8PiZtDFj7PcYWirnY/GExiyI7Cr7bQKaMfDIz6f92cefxINf3+4g+SmHQw0D6eV3LaD9h/V882S

Steps to reproduce

Steps to Reproduce

  1. Define a Vue 3 SFC with <script setup lang="ts">.
  2. Declare a prop using defineProps with a union type OverflowType | boolean, where OverflowType is a string literal union 'tooltip' | 'ellipsis' | 'title'.
  3. Set a default value of undefined using withDefaults for that prop.
  4. In a parent component, use the child component with the prop as an attribute without a value: <MyComponent showOverflow />.
  5. Observe the actual value of the prop.
<!-- Child.vue -->
<template>
  <div>showOverflow = {{ JSON.stringify(showOverflow) }} (type: {{ typeof showOverflow }})</div>
</template>

<script setup lang="ts">
type OverflowType = 'tooltip' | 'ellipsis' | 'title'

// Case A: boolean placed first
const propsA = withDefaults(defineProps<{ showOverflow?: boolean | OverflowType }>(), { showOverflow: undefined })

// Case B: OverflowType placed first
const propsB = withDefaults(defineProps<{ showOverflow?: OverflowType | boolean }>(), { showOverflow: undefined })
</script>
<!-- Parent.vue -->
<template>
  <ChildA showOverflow />
  <!-- Case A -->
  <ChildB showOverflow />
  <!-- Case B -->
</template>

What is expected?

Expected Result

Regardless of the order of types in the union, the behavior for the same prop when passed as an attribute without a value should be consistent. Users would expect either:

  • Always pass true (aligning with the HTML boolean attribute semantics – <input disabled> means true); or
  • Always pass "" (empty string), as an attribute without a value in HTML is essentially an empty string attribute.

But the behavior should never depend on the order of type declarations.

More importantly, the user has explicitly set a default value of undefined via withDefaults. The expected behavior is:

  • When the parent does not pass the prop at all → use undefined.
  • When the parent passes the prop as an attribute without a value → it should be treated as an explicit "presence" signal, and thus the value should be true (consistent with boolean attribute semantics).

What is actually happening?

Actual Result

  • Case A (boolean | OverflowType): showOverflow is true (boolean)
  • Case B (OverflowType | boolean): showOverflow is "" (string)

Both results ignore the default value undefined set via withDefaults, because the prop is considered "passed" (even without a value), so the default is not applied.

System Info

System:
    OS: Windows 11 10.0.22631
    CPU: (28) x64 Intel(R) Core(TM) i7-14700
    Memory: 9.05 GB / 31.68 GB
  Binaries:
  Binaries:
    Node: 20.20.2 - C:\install\nodejs\v20.20.2\node.EXE
    npm: 10.8.2 - C:\install\nodejs\v20.20.2\npm.CMD
  Browsers:
    Chrome: 128.0.6613.85
    Edge: Chromium (149.0.4022.80)
  npmPackages:
    vue: ^3.2.41 => 3.5.39

Any additional comments?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions