Skip to content

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 constructorsBoolean, 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

vue
<!-- Comp.vue -->
<script setup lang="ts">
import { Props } from "./types";

withDefaults(defineProps<Props>(), {
  disabled: false,
  sampleBooleanProp: false
});
</script>
ts
// types.ts
import { InputHTMLAttributes } from "vue";

export interface Props {
  disabled?: InputHTMLAttributes["disabled"] // imported type
  sampleBooleanProp?: boolean
}

Result

html
<!-- 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.

ts
// 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!