Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/ts-defineprops-boolean-default-false.md
1---2title: Boolean Props Default to false, Not undefined3impact: MEDIUM4impactDescription: TypeScript expects optional boolean to be undefined but Vue defaults it to false, causing type confusion5type: gotcha6tags: [vue3, typescript, props, boolean, defineProps]7---89# Boolean Props Default to false, Not undefined1011**Impact: MEDIUM** - When using type-based `defineProps`, optional boolean props (marked with `?`) behave differently than TypeScript expects. Vue treats boolean props specially: an absent boolean prop defaults to `false`, not `undefined`. This can cause confusion when TypeScript thinks the type is `boolean | undefined`.1213## Task Checklist1415- [ ] Understand that Vue's boolean casting makes absent booleans `false`16- [ ] Use `withDefaults()` to be explicit about boolean defaults17- [ ] Consider using non-boolean types if `undefined` is a meaningful state18- [ ] Document this Vue-specific behavior for your team1920## The Gotcha2122```vue23<script setup lang="ts">24interface Props {25disabled?: boolean // TypeScript sees: boolean | undefined26}2728const props = defineProps<Props>()2930// TypeScript thinks props.disabled could be undefined31if (props.disabled === undefined) {32console.log('This will NEVER run!')33// Vue's boolean casting means disabled is false, not undefined34}35</script>3637<template>38<!-- When used without the prop -->39<MyComponent />40<!-- disabled is false, NOT undefined -->41</template>42```4344## Why This Happens4546Vue has special "boolean casting" behavior inherited from HTML boolean attributes:4748```vue49<!-- All of these make disabled = true -->50<MyComponent disabled />51<MyComponent :disabled="true" />52<MyComponent disabled="" />5354<!-- This makes disabled = false (NOT undefined) -->55<MyComponent />5657<!-- Explicit false -->58<MyComponent :disabled="false" />59```6061This is by design to match how HTML works:62```html63<!-- HTML: presence means true, absence means false -->64<button disabled>Can't click</button>65<button>Can click</button>66```6768## Solutions6970### Solution 1: Be Explicit with withDefaults7172Make your intention clear:7374```vue75<script setup lang="ts">76interface Props {77disabled?: boolean78}7980// Explicitly document the default81const props = withDefaults(defineProps<Props>(), {82disabled: false // Now it's clear this defaults to false83})84</script>85```8687### Solution 2: Use a Three-State Type8889If you actually need to distinguish "not set" from "explicitly false":9091```vue92<script setup lang="ts">93interface Props {94// Use a union type instead of optional boolean95state?: 'enabled' | 'disabled' | undefined9697// Or use undefined explicitly98toggleState?: boolean | undefined99}100101const props = withDefaults(defineProps<Props>(), {102state: undefined, // Can actually be undefined103toggleState: undefined104})105106// Now you can check for undefined107if (props.state === undefined) {108// Use parent's state109} else if (props.state === 'disabled') {110// Explicitly disabled111}112</script>113```114115### Solution 3: Use null for "Not Set"116117```vue118<script setup lang="ts">119interface Props {120// null = not set, false = explicitly off, true = explicitly on121selected: boolean | null122}123124const props = withDefaults(defineProps<Props>(), {125selected: null126})127128// Three distinct states129if (props.selected === null) {130console.log('Selection not specified')131} else if (props.selected) {132console.log('Selected')133} else {134console.log('Explicitly not selected')135}136</script>137```138139## Boolean Casting Order140141Vue also has special behavior when Boolean and String are both valid:142143```typescript144// Order matters in runtime declaration!145defineProps({146// Boolean first: empty string becomes true147disabled: [Boolean, String]148})149150// <MyComponent disabled /> → disabled = true151// <MyComponent disabled="" /> → disabled = true152```153154```typescript155defineProps({156// String first: empty string stays as string157disabled: [String, Boolean]158})159160// <MyComponent disabled /> → disabled = ''161// <MyComponent disabled="" /> → disabled = ''162```163164With type-based declaration, Boolean always takes priority for absent props.165166## Common Bug Pattern167168```vue169<!-- Parent.vue -->170<script setup lang="ts">171const userPreferences = ref({172darkMode: undefined as boolean | undefined173})174175// Fetch preferences...176onMounted(async () => {177userPreferences.value = await fetchPreferences()178})179</script>180181<template>182<!-- Bug: undefined becomes false, not "inherit system preference" -->183<ThemeToggle :darkMode="userPreferences.darkMode" />184</template>185```186187**Fix:**188189```vue190<script setup lang="ts">191const userPreferences = ref<{192darkMode: boolean | null193}>({194darkMode: null // Use null for "not yet loaded"195})196</script>197198<template>199<!-- Now ThemeToggle can distinguish between null and false -->200<ThemeToggle :darkMode="userPreferences.darkMode" />201</template>202```203204## TypeScript Type Accuracy205206The Vue type system handles this, but it can be confusing:207208```typescript209interface Props {210disabled?: boolean211}212213const props = defineProps<Props>()214215// At compile time: boolean | undefined216// At runtime: boolean (never undefined due to Vue's boolean casting)217218// TypeScript is technically "wrong" here, but the withDefaults usage219// or explicit false default can help align expectations220```221222## Reference223- [Vue.js Props - Boolean Casting](https://vuejs.org/guide/components/props.html#boolean-casting)224- [GitHub Issue: Boolean props default to false](https://github.com/vuejs/core/issues/8576)225- [TypeScript Vue 3 Props](https://madewithlove.com/blog/typescript-vue-3-and-strongly-typed-props/)226