vuetifyjs/vuetify · error · Error

[Vuetify] v-expansion-panel-title needs to be placed inside

Error message

[Vuetify] v-expansion-panel-title needs to be placed inside v-expansion-panel

What it means

VExpansionPanelTitle injects VExpansionPanelSymbol, provided only by <v-expansion-panel>. It throws when <v-expansion-panel-title> is rendered without a <v-expansion-panel> ancestor, since the title needs the panel's selection/expanded state.

Source

Thrown at packages/vuetify/src/components/VExpansionPanel/VExpansionPanelTitle.tsx:73

    type: Boolean,
    default: true,
  },

  ...makeComponentProps(),
  ...makeDimensionProps(),
}, 'VExpansionPanelTitle')

export const VExpansionPanelTitle = genericComponent<VExpansionPanelTitleSlots>()({
  name: 'VExpansionPanelTitle',

  directives: { vRipple },

  props: makeVExpansionPanelTitleProps(),

  setup (props, { slots }) {
    const expansionPanel = inject(VExpansionPanelSymbol)

    if (!expansionPanel) throw new Error('[Vuetify] v-expansion-panel-title needs to be placed inside v-expansion-panel')

    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.color)
    const { dimensionStyles } = useDimension(props)

    const slotProps = computed(() => ({
      collapseIcon: props.collapseIcon,
      disabled: expansionPanel.disabled.value,
      expanded: expansionPanel.isSelected.value,
      expandIcon: props.expandIcon,
      readonly: props.readonly,
    }))

    const icon = toRef(() => expansionPanel.isSelected.value ? props.collapseIcon : props.expandIcon)

    useRender(() => (
      <button
        class={[
          'v-expansion-panel-title',

View on GitHub (pinned to 8d153908df)

Solutions

  1. Nest <v-expansion-panel-title> inside <v-expansion-panel> (inside <v-expansion-panels>).
  2. Keep teleported title nodes inside the panel provide tree.
  3. Wrap the component in <v-expansion-panel> for tests.

Example fix

// before
<v-expansion-panel-title>Title</v-expansion-panel-title>

// after
<v-expansion-panels>
  <v-expansion-panel>
    <v-expansion-panel-title>Title</v-expansion-panel-title>
  </v-expansion-panel>
</v-expansion-panels>
Defensive patterns

Strategy: type-guard

Validate before calling

import { inject } from 'vue'
import { VExpansionPanelSymbol } from 'vuetify'
const panel = inject(VExpansionPanelSymbol, null)
if (!panel) {
  // not inside a panel: render a plain button instead of v-expansion-panel-title
}

Type guard

import { inject } from 'vue'
import { VExpansionPanelSymbol } from 'vuetify'
export function isInsideExpansionPanel (): boolean {
  return inject(VExpansionPanelSymbol, null) != null
}

Prevention

When it happens

Trigger: Placing <v-expansion-panel-title> outside <v-expansion-panel>; teleporting it out of the panel tree; mounting it alone in a test.

Common situations: Markup restructuring that removes the panel wrapper; copy-paste of a title block into another container; building panels dynamically and omitting the wrapper.

Related errors


AI-assisted analysis of vuetifyjs/vuetify@8d153908df (2026-08-12). Data as JSON: /api/errors/0806796f230654bc. Report an issue: GitHub.