Vue, TypeScript, and imported types: what could go wrong?
When you use defineProps
in Vue with TypeScript, the Vue compiler performs reverse type generation: from interfaces, annotations, and default values (default
), it generates JS constructors — Boolean
, String
, Object
, and so on. These constructors are then used in the compiled component at runtime.
The compiler will do its best to infer equivalent runtime parameters based on the type arguments. But this doesn't always work.
Problem: Boolean breaks when imported
As of vue@3.5.*
, if prop types are imported rather than defined directly inside the component, Vue may incorrectly infer the Boolean
type when using shorthand
syntax.
Example
<!-- Comp.vue -->
<script setup lang="ts">
import { Props } from "./types";
withDefaults(defineProps<Props>(), {
disabled: false,
sampleBooleanProp: false
});
</script>
// types.ts
import { InputHTMLAttributes } from "vue";
export interface Props {
disabled?: InputHTMLAttributes["disabled"] // imported type
sampleBooleanProp?: boolean
}
Result
<!-- Props not provided. Defaults should apply -->
<Comp/>
<!-- ✅ Works as expected. disabled: false, sampleBooleanProp: false -->
<!-- Props explicitly set to true -->
<Comp :disabled="true" :sampleBooleanProp="true"/>
<!-- ✅ Works as expected. disabled: true, sampleBooleanProp: true -->
<!-- Props explicitly set to false -->
<Comp disabled="false" sampleBooleanProp="false"/>
<!-- ✅ Works as expected. disabled: false, sampleBooleanProp: false -->
<!-- Shorthand syntax should imply true -->
<Comp disabled sampleBooleanProp/>
<!-- ❌ Bug. disabled: '' (empty string), sampleBooleanProp: true -->
As shown, when the disabled
prop is used without a value, Vue sets it to an empty string rather than the expected true
. This means Vue didn't generate a Boolean
constructor for disabled
— it failed to infer the correct type from the imported interface.
Fix
If you switch to a locally declared type, everything works as intended.
// types.ts
// local interface, identical to InputHTMLAttributes
interface InputHTMLAttributes_Local { disabled: boolean | "true" | "false" }
export interface Props {
disabled?: InputHTMLAttributes_Local["disabled"] // local equivalent
sampleBooleanProp?: boolean
}
In this case, TypeScript passes more complete information about local types to Vue AST, and Vue correctly uses the Boolean
constructor.
Try it yourself
If you want to experiment: Open demo on Vue Playground
Conclusion
If your shorthand
props behave strangely, the problem is likely caused by Vue failing to infer types from imported interfaces. For now, the only solution is to declare types locally or avoid shorthand
syntax.
If you know another workaround — let us know, we'd love to dig deeper!