Disable slots using defineSlots
#14461
Replies: 4 comments 3 replies
-
|
@rtcpw currently maintaining this approach? However, defineSlots<{}>() // weak
defineSlots<Record<string, never>>() // stronger intent |
Beta Was this translation helpful? Give feedback.
-
|
I do not think
defineSlots<{
default(props: { item: string }): any;
}>();Using an empty object: defineSlots<{}>();means “this component declares no typed slots”, but Vue template type checking generally does not treat arbitrary children as a hard error just because no slots are declared. At runtime, Vue will still accept children and expose them on So this is not something I would expect to be caught consistently: <ComponentA>
<div>Hello World</div>
</ComponentA>If you want a runtime development warning, you can check <script setup lang="ts">
import { useSlots, onMounted } from "vue";
const slots = useSlots();
onMounted(() => {
if (import.meta.env.DEV && slots.default) {
console.warn("ComponentA does not accept slot content.");
}
});
</script>Or, without waiting for mount: const slots = useSlots();
if (import.meta.env.DEV && slots.default) {
console.warn("ComponentA does not accept slot content.");
}That will not be a TypeScript error, but it gives consumers quick feedback during development. For library authoring, I would document it as a renderless component with no slot API, and optionally add the dev warning. Today, |
Beta Was this translation helpful? Give feedback.
-
|
Has this changed, or perhaps im misunderstanding something. If i try to use a named slot that is not in defined slots I will actually get a Also is it intentional that this works by only checking inclusion? I.e. if i try to define type slotsType =
| ({a : boolean} & {b: never))
| ({a : never} & {b: number))using both a and b slots will not raise an error. To give context on where the 2nd scenario can be useful: im trying to make a datatable where you can either override how certain cells are rendered, or get an escape hatch for the entire row, using both at the same time is not possible |
Beta Was this translation helpful? Give feedback.
-
I think there are two different layers involved here, and that is why the behavior can look a bit inconsistent depending on the tool/version being used.
`defineSlots` is a type macro. In Vue core / `@vue/compiler-sfc`, it does not create a runtime slot contract and it does not make the SFC compiler reject children passed to the component.
In the current Vue core implementation, `defineSlots` is described as:
> Vue `<script setup>` compiler macro for providing type hints to IDEs for slot name and slot props type checking.
The macro is compiled away. If its return value is assigned, the SFC compiler rewrites it to `useSlots()`:
```ts
const slots = defineSlots<{
default(props: { msg: string }): any
}>()
```
becomes, effectively:
```ts
const slots = useSlots()
```
If the return value is not used:
```ts
defineSlots<{
default(props: { msg: string }): any
}>()
```
then the macro disappears entirely from the generated output.
So at Vue runtime level, this:
```ts
defineSlots<{}>()
```
does **not** mean "reject all children passed to this component". Vue will still accept:
```vue
<ComponentA>
<div>Hello World</div>
</ComponentA>
```
The passed content becomes a slot on the component instance. If `ComponentA` never renders `$slots.default`, the content is simply ignored.
The errors people are seeing for undeclared slots come from `vue-tsc` / `@vue/language-tools`, not from Vue core runtime behavior. Newer language-tools versions can flag some slots that are not declared by `defineSlots`, especially named slots. That means `defineSlots<{}>()` can be useful as a type-level signal saying "this component exposes no public slots", provided consumers are checked by `vue-tsc` or an IDE using the same language-tools behavior. I would still treat rejection of implicit default-slot children as language-tools/version-dependent, not as a Vue core guarantee.
For expressing "no slots", I would use:
```ts
defineSlots<{}>()
```
rather than:
```ts
defineSlots<Record<string, never>>()
```
`Record<string, never>` does not really mean "there are no slot names". It describes every possible string key as having the value type `never`, which is a different type-level shape. For a public component API, `{}` communicates the intended contract more directly: this component declares no slots.
For the union example:
```ts
type Slots =
| ({ a: boolean } & { b: never })
| ({ a: never } & { b: number })
```
I would not expect current template slot checking to fully model "the provided slot set must satisfy exactly one union branch". The current behavior is closer to checking whether each provided slot name is known/present on the exposed slots type. That explains why using both `a` and `b` may not raise an error even though the intended API is mutually exclusive.
For a datatable API where consumers may provide either cell-level slots or a whole-row escape hatch, I would not rely on `defineSlots` unions to enforce that exclusivity today. A few practical options are:
1. Use `defineSlots` for the positive slot names and slot prop types.
2. Add a dev-only runtime assertion if mutually exclusive slots are passed together.
3. Consider modeling the choice through an explicit prop/API if the distinction is central to the component behavior.
4. Open an issue/feature request in `vuejs/language-tools` for stricter slot-set validation, since that diagnostic behavior belongs there rather than in Vue core.
So my current understanding is:
- `defineSlots<{}>()` is the clearest type-level declaration for "this component has no public slots".
- Vue core does not enforce that at runtime or at SFC compile time.
- `vue-tsc` / language-tools may report undeclared slots, depending on version and template checking behavior, but implicit default-slot children are not something Vue core itself rejects.
- Mutually exclusive slot groups are not reliably enforced by the current slot checker.
- If stronger compile-time checking is desired, the right place to improve it is `vuejs/language-tools`.Local Repo NotesThese are the core details I checked in the cloned Vue repo:
Short VersionIf you want a shorter comment, post this: `defineSlots<{}>()` is the clearest way to express "this component exposes no public slots" at the type level, but Vue core itself does not enforce that as a runtime or SFC-compile-time contract. `defineSlots` is a type macro; it is compiled away, or rewritten to `useSlots()` if the return value is assigned.
So slot errors for undeclared slots come from `vue-tsc` / `@vue/language-tools`, not from Vue runtime. Newer language-tools versions can flag undeclared named slots, including when the component declares an empty slot type, but I would treat implicit default-slot children as version/tooling-dependent and would not expect current tooling to fully validate mutually exclusive slot unions like "slot `a` or slot `b`, but not both". That kind of stricter slot-set validation would need to be handled in `vuejs/language-tools`.
For "no slots", I would prefer `defineSlots<{}>()` over `defineSlots<Record<string, never>>()`, because `Record<string, never>` describes every string key with value `never`; it is not the same shape as "no declared slot keys". |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I have a renderless component that does not use slots. I would like to specify that there are no slots using
defineSlots, so that TypeScript users can catch the error early if they specify a slot content. Is there a way to do this? This is what I want:In ComponentA:
In ComponentB:
Beta Was this translation helpful? Give feedback.
All reactions