{"record":{"id":"0806796f230654bc","repo":"vuetifyjs/vuetify","slug":"vuetify-v-expansion-panel-title-needs-to-be-plac","errorCode":null,"errorMessage":"[Vuetify] v-expansion-panel-title needs to be placed inside v-expansion-panel","messagePattern":"\\[Vuetify\\] v-expansion-panel-title needs to be placed inside v-expansion-panel","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vuetify/src/components/VExpansionPanel/VExpansionPanelTitle.tsx","lineNumber":73,"sourceCode":"    type: Boolean,\n    default: true,\n  },\n\n  ...makeComponentProps(),\n  ...makeDimensionProps(),\n}, 'VExpansionPanelTitle')\n\nexport const VExpansionPanelTitle = genericComponent<VExpansionPanelTitleSlots>()({\n  name: 'VExpansionPanelTitle',\n\n  directives: { vRipple },\n\n  props: makeVExpansionPanelTitleProps(),\n\n  setup (props, { slots }) {\n    const expansionPanel = inject(VExpansionPanelSymbol)\n\n    if (!expansionPanel) throw new Error('[Vuetify] v-expansion-panel-title needs to be placed inside v-expansion-panel')\n\n    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.color)\n    const { dimensionStyles } = useDimension(props)\n\n    const slotProps = computed(() => ({\n      collapseIcon: props.collapseIcon,\n      disabled: expansionPanel.disabled.value,\n      expanded: expansionPanel.isSelected.value,\n      expandIcon: props.expandIcon,\n      readonly: props.readonly,\n    }))\n\n    const icon = toRef(() => expansionPanel.isSelected.value ? props.collapseIcon : props.expandIcon)\n\n    useRender(() => (\n      <button\n        class={[\n          'v-expansion-panel-title',","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/vuetifyjs/vuetify/blob/8d153908dffb7592eb389ddbfbab955a0ccc977f/packages/vuetify/src/components/VExpansionPanel/VExpansionPanelTitle.tsx#L55-L91","documentation":"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.","triggerScenarios":"Placing <v-expansion-panel-title> outside <v-expansion-panel>; teleporting it out of the panel tree; mounting it alone in a test.","commonSituations":"Markup restructuring that removes the panel wrapper; copy-paste of a title block into another container; building panels dynamically and omitting the wrapper.","solutions":["Nest <v-expansion-panel-title> inside <v-expansion-panel> (inside <v-expansion-panels>).","Keep teleported title nodes inside the panel provide tree.","Wrap the component in <v-expansion-panel> for tests."],"exampleFix":"// before\n<v-expansion-panel-title>Title</v-expansion-panel-title>\n\n// after\n<v-expansion-panels>\n  <v-expansion-panel>\n    <v-expansion-panel-title>Title</v-expansion-panel-title>\n  </v-expansion-panel>\n</v-expansion-panels>","handlingStrategy":"type-guard","validationCode":"import { inject } from 'vue'\nimport { VExpansionPanelSymbol } from 'vuetify'\nconst panel = inject(VExpansionPanelSymbol, null)\nif (!panel) {\n  // not inside a panel: render a plain button instead of v-expansion-panel-title\n}","typeGuard":"import { inject } from 'vue'\nimport { VExpansionPanelSymbol } from 'vuetify'\nexport function isInsideExpansionPanel (): boolean {\n  return inject(VExpansionPanelSymbol, null) != null\n}","tryCatchPattern":null,"preventionTips":["Always nest the title inside <v-expansion-panel>.","Do not teleport the title outside the panel tree.","Wrap the title in <v-expansion-panel> for tests."],"tags":["injection","expansion-panel","components"],"backgroundTag":null,"analyzedSha":"8d153908dffb7592eb389ddbfbab955a0ccc977f","analyzedAt":"2026-08-12T21:54:20.596Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}