skip to content

In Vue 3, what does v-bind render for false, null and '' on a boolean attribute like disabled versus on aria-pressed?

level: middleimportance: should knowfreq 42%

answer

  1. presence means true for boolean attributes
  2. empty string counts as present
  3. Vue 3 changed false coercion
  4. null or undefined removes

basics

~20 s

For boolean attributes such as disabled, Vue 3 includes the attribute for truthy values and '' and omits it for other falsy values. For other attributes, false renders as the string "false"; only null or undefined remove them.

solid answer

~30 s

Boolean attributes mean "on" by presence, so `:disabled="isDisabled"` adds `disabled` when the value is truthy **or** `''` (matching `<button disabled="">`) and removes it for `false`, `null`, `undefined` or `0`. Everything else is a plain value: in Vue 3, `:aria-pressed="false"` renders `aria-pressed="false"`, which is what assistive technology needs. To remove a non-boolean attribute you bind `null` or `undefined`. This was a Vue 3 breaking change: Vue 2 removed any attribute bound to `false`, so `aria-*` values had to be stringified by hand. The practical trap is the reverse: a value like `:data-active="isActive && 'yes'"` renders `data-active="false"` instead of disappearing.

go deeper

for a junior

Recall that :disabled is added for truthy values and removed for false, and that null removes other attributes.

for a middle

Explain the Vue 3 split: boolean attributes by presence, non-boolean attributes keep false as the string "false".

for a senior

Catch the data-* and aria-* regressions from Vue 2-era code, and the truthy 'false' string reaching a boolean attribute.

for a principal

Standardise how shared components expose state attributes — explicit aria values, null to omit — so accessibility and CSS selectors stay reliable.

## Two kinds of attribute HTML has **boolean attributes** — `disabled`, `readonly`, `required`, `checked`, `hidden`, `open` and so on — whose meaning is carried by **presence**: `<button disabled>` and `<button disabled="false">` are both disabled. Every other attribute (`aria-pressed`, `title`, `data-*`, `href`) carries a **value**. Vue 3's `v-bind` treats the two differently, and the Vue guide spells out the boolean rule: the attribute is included if the value is truthy, and also if it is an empty string (for consistency with `<button disabled="">`); for other falsy values it is omitted. ## The rendering table | Bound value | `:disabled` (boolean) | `:aria-pressed` (non-boolean) | |---|---|---| | `true` | present | `aria-pressed="true"` | | `false` | **absent** | **`aria-pressed="false"`** | | `''` | present | `aria-pressed=""` | | `0` | absent | `aria-pressed="0"` | | `null` / `undefined` | absent | **absent** | | `'false'` (string) | present | `aria-pressed="false"` | Three rows deserve attention: 1. **`false` on a non-boolean attribute is rendered as the string `"false"`.** That is deliberate: ARIA attributes such as `aria-pressed`, `aria-expanded` and `aria-selected` need an explicit `"false"`. 2. **`''` counts as present** for boolean attributes. 3. **The string `'false'`** makes a boolean attribute present — and therefore on — because only presence counts. ## How Vue gets there Two runtime paths are involved: - For most elements, `disabled`, `checked` and friends also exist as **DOM properties**, so Vue sets the property (`button.disabled = false`), and the browser reflects it as attribute presence. An empty string is normalised to `true` for boolean properties. - For boolean attributes whose DOM property is spelled differently or does not exist (`readonly` vs `readOnly`, `novalidate` vs `noValidate`, `itemscope`), Vue's attribute path removes the attribute unless the value is truthy or `''`. - For all other attributes, Vue calls `setAttribute(name, value)` — stringifying `false` to `"false"` — and calls `removeAttribute` only for `null` or `undefined`. ## A toggle button done right ```vue <script setup lang="ts"> import { ref } from 'vue' const pressed = ref(false) const busy = ref(false) </script> <template> <button type="button" :aria-pressed="pressed" :disabled="busy" :title="busy ? 'Saving…' : null" @click="pressed = !pressed" > Bold </button> </template> ``` - `aria-pressed` always renders `"true"` or `"false"`, so screen readers announce a toggle state. - `disabled` appears only while `busy` is true. - `title` disappears when not needed, because `null` removes it. ## The Vue 2 difference Vue 2 removed **any** attribute bound to `false`, which forced `:aria-pressed="String(pressed)"` workarounds. The Vue 3 migration guide lists this as a breaking change: non-boolean attributes are no longer removed for `false`; bind `null` or `undefined` to remove them. Code ported from Vue 2 that relied on `false` to hide an attribute now renders `attr="false"`. ## Mistakes interviewers listen for - `:data-state="isOpen && 'open'"` — renders `data-state="false"` when closed; a CSS selector `[data-state]` then matches both states. Use `isOpen ? 'open' : null`. - `:disabled="'false'"` from a string prop — the button is disabled. - `:aria-hidden="hidden"` expected to vanish when `false` — it renders `aria-hidden="false"`, which is valid and usually what you want.

  • In Vue 3, how do you make a non-boolean attribute such as title disappear through v-bind?
    Bind `null` or `undefined`. Vue removes the attribute only for those two values; `false` is rendered as the string `"false"`. A ternary like `:title="busy ? 'Saving…' : null"` keeps the attribute only while it has something to say.
  • In Vue 3, why does <button :disabled="disabledProp"> stay disabled when the parent passes the string 'false'?
    `disabled` is a boolean attribute: only presence matters, and Vue keeps it for any truthy value. The non-empty string `'false'` is truthy, so the button is disabled. The fix is at the source: the parent should pass a real boolean (`:disabled="false"`, with the colon) rather than the string.

saying these in an interview costs you the question

  • In Vue 3, binding false to any attribute removes it.
  • :disabled="''" leaves the button enabled because the string is empty.
  • :aria-expanded="false" makes Vue drop aria-expanded.
  • The string 'false' keeps a boolean attribute off.
  • Vue 3 kept Vue 2's rule of removing attributes bound to false.