tailwindlabs/headlessui · error · Error

A <DialogBackdrop /> component is being used, but a <DialogP

Error message

A <DialogBackdrop /> component is being used, but a <DialogPanel /> component is missing.

What it means

When @headlessui-vue's DialogBackdrop mounts, it expects a sibling DialogPanel to exist inside the same Dialog (it wires backdrop click handling and aria wiring against api.panelRef). If panelRef is still null onMounted, the required <DialogPanel /> is missing and the component throws because the backdrop cannot function correctly without it.

Source

Thrown at packages/@headlessui-vue/src/components/dialog/dialog.ts:405

// ---

export let DialogBackdrop = defineComponent({
  name: 'DialogBackdrop',
  props: {
    as: { type: [Object, String], default: 'div' },
    id: { type: String, default: () => `headlessui-dialog-backdrop-${useId()}` },
  },
  inheritAttrs: false,
  setup(props, { attrs, slots, expose }) {
    let api = useDialogContext('DialogBackdrop')
    let internalBackdropRef = ref(null)

    expose({ el: internalBackdropRef, $el: internalBackdropRef })

    onMounted(() => {
      if (api.panelRef.value === null) {
        throw new Error(
          `A <DialogBackdrop /> component is being used, but a <DialogPanel /> component is missing.`
        )
      }
    })

    return () => {
      let { id, ...theirProps } = props
      let ourProps = {
        id,
        ref: internalBackdropRef,
        'aria-hidden': true,
      }

      return h(ForcePortalRoot, { force: true }, () =>
        h(Portal, () =>
          render({
            ourProps,
            theirProps: { ...attrs, ...theirProps },

View on GitHub (pinned to eea57cf46f)

Solutions

  1. Add a <DialogPanel> inside the same <Dialog>, containing the dialog's content.
  2. If you replaced DialogPanel with a plain div, switch it back to DialogPanel and style it via its props/class.
  3. Ensure DialogPanel is not wrapped in v-if="false" or lazy-rendered so it exists when the backdrop mounts.

Example fix

// before
<Dialog :open="open">
  <DialogBackdrop class="bg-black/50" />
  <div class="...">...</div>
</Dialog>

// after
<Dialog :open="open">
  <DialogBackdrop class="bg-black/50" />
  <DialogPanel class="...">...</DialogPanel>
</Dialog>
Defensive patterns

Strategy: validation

Validate before calling

// Only render the backdrop when the panel is present in the same Dialog
<Dialog :open="open">
  <DialogBackdrop class="bg-black/50" />
  <DialogPanel v-if="hasContent"><slot /></DialogPanel>
</Dialog>

Prevention

When it happens

Trigger: Rendering <DialogBackdrop> inside <Dialog> but omitting <DialogPanel>; replacing DialogPanel with a plain <div>; ordering/mounting issues where the panel component is conditionally not rendered.

Common situations: Refactoring dialog markup and dropping the DialogPanel wrapper; using DialogBackdrop for styling but keeping content in raw divs; upgrading from patterns where panels were implicit.

Related errors


AI-assisted analysis of tailwindlabs/headlessui@eea57cf46f (2026-08-28). Data as JSON: /api/errors/3624c89758dd0e98. Report an issue: GitHub.