Files
agent-skills/skills/vue-best-practices/reference/ts-defineprops-boolean-default-false.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

226 lines
5.4 KiB
Markdown

---
title: Boolean Props Default to false, Not undefined
impact: MEDIUM
impactDescription: TypeScript expects optional boolean to be undefined but Vue defaults it to false, causing type confusion
type: gotcha
tags: [vue3, typescript, props, boolean, defineProps]
---
# Boolean Props Default to false, Not undefined
**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`.
## Task Checklist
- [ ] Understand that Vue's boolean casting makes absent booleans `false`
- [ ] Use `withDefaults()` to be explicit about boolean defaults
- [ ] Consider using non-boolean types if `undefined` is a meaningful state
- [ ] Document this Vue-specific behavior for your team
## The Gotcha
```vue
<script setup lang="ts">
interface Props {
disabled?: boolean // TypeScript sees: boolean | undefined
}
const props = defineProps<Props>()
// TypeScript thinks props.disabled could be undefined
if (props.disabled === undefined) {
console.log('This will NEVER run!')
// Vue's boolean casting means disabled is false, not undefined
}
</script>
<template>
<!-- When used without the prop -->
<MyComponent />
<!-- disabled is false, NOT undefined -->
</template>
```
## Why This Happens
Vue has special "boolean casting" behavior inherited from HTML boolean attributes:
```vue
<!-- All of these make disabled = true -->
<MyComponent disabled />
<MyComponent :disabled="true" />
<MyComponent disabled="" />
<!-- This makes disabled = false (NOT undefined) -->
<MyComponent />
<!-- Explicit false -->
<MyComponent :disabled="false" />
```
This is by design to match how HTML works:
```html
<!-- HTML: presence means true, absence means false -->
<button disabled>Can't click</button>
<button>Can click</button>
```
## Solutions
### Solution 1: Be Explicit with withDefaults
Make your intention clear:
```vue
<script setup lang="ts">
interface Props {
disabled?: boolean
}
// Explicitly document the default
const props = withDefaults(defineProps<Props>(), {
disabled: false // Now it's clear this defaults to false
})
</script>
```
### Solution 2: Use a Three-State Type
If you actually need to distinguish "not set" from "explicitly false":
```vue
<script setup lang="ts">
interface Props {
// Use a union type instead of optional boolean
state?: 'enabled' | 'disabled' | undefined
// Or use undefined explicitly
toggleState?: boolean | undefined
}
const props = withDefaults(defineProps<Props>(), {
state: undefined, // Can actually be undefined
toggleState: undefined
})
// Now you can check for undefined
if (props.state === undefined) {
// Use parent's state
} else if (props.state === 'disabled') {
// Explicitly disabled
}
</script>
```
### Solution 3: Use null for "Not Set"
```vue
<script setup lang="ts">
interface Props {
// null = not set, false = explicitly off, true = explicitly on
selected: boolean | null
}
const props = withDefaults(defineProps<Props>(), {
selected: null
})
// Three distinct states
if (props.selected === null) {
console.log('Selection not specified')
} else if (props.selected) {
console.log('Selected')
} else {
console.log('Explicitly not selected')
}
</script>
```
## Boolean Casting Order
Vue also has special behavior when Boolean and String are both valid:
```typescript
// Order matters in runtime declaration!
defineProps({
// Boolean first: empty string becomes true
disabled: [Boolean, String]
})
// <MyComponent disabled /> → disabled = true
// <MyComponent disabled="" /> → disabled = true
```
```typescript
defineProps({
// String first: empty string stays as string
disabled: [String, Boolean]
})
// <MyComponent disabled /> → disabled = ''
// <MyComponent disabled="" /> → disabled = ''
```
With type-based declaration, Boolean always takes priority for absent props.
## Common Bug Pattern
```vue
<!-- Parent.vue -->
<script setup lang="ts">
const userPreferences = ref({
darkMode: undefined as boolean | undefined
})
// Fetch preferences...
onMounted(async () => {
userPreferences.value = await fetchPreferences()
})
</script>
<template>
<!-- Bug: undefined becomes false, not "inherit system preference" -->
<ThemeToggle :darkMode="userPreferences.darkMode" />
</template>
```
**Fix:**
```vue
<script setup lang="ts">
const userPreferences = ref<{
darkMode: boolean | null
}>({
darkMode: null // Use null for "not yet loaded"
})
</script>
<template>
<!-- Now ThemeToggle can distinguish between null and false -->
<ThemeToggle :darkMode="userPreferences.darkMode" />
</template>
```
## TypeScript Type Accuracy
The Vue type system handles this, but it can be confusing:
```typescript
interface Props {
disabled?: boolean
}
const props = defineProps<Props>()
// At compile time: boolean | undefined
// At runtime: boolean (never undefined due to Vue's boolean casting)
// TypeScript is technically "wrong" here, but the withDefaults usage
// or explicit false default can help align expectations
```
## Reference
- [Vue.js Props - Boolean Casting](https://vuejs.org/guide/components/props.html#boolean-casting)
- [GitHub Issue: Boolean props default to false](https://github.com/vuejs/core/issues/8576)
- [TypeScript Vue 3 Props](https://madewithlove.com/blog/typescript-vue-3-and-strongly-typed-props/)