tailwindlabs/headlessui · error · Error

You forgot to provide an `open` prop to the `Dialog`.

Error message

You forgot to provide an `open` prop to the `Dialog`.

What it means

The @headlessui-vue Dialog requires a controlled open state. In setup() it checks whether the open prop was provided or whether the Dialog is inside an OpenClosed-state provider (like Popover or Disclosure panels that render Dialog-like overlays). If neither is present, the Dialog cannot know whether it is open, so it throws this validation error.

Source

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

    let usesOpenClosedState = useOpenClosed()
    let open = computed(() => {
      if (props.open === Missing && usesOpenClosedState !== null) {
        return (usesOpenClosedState.value & State.Open) === State.Open
      }
      return props.open
    })

    let internalDialogRef = ref<HTMLDivElement | null>(null)

    let ownerDocument = computed(() => getOwnerDocument(internalDialogRef))

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

    // Validations
    let hasOpen = props.open !== Missing || usesOpenClosedState !== null

    if (!hasOpen) {
      throw new Error(`You forgot to provide an \`open\` prop to the \`Dialog\`.`)
    }

    if (typeof open.value !== 'boolean') {
      throw new Error(
        `You provided an \`open\` prop to the \`Dialog\`, but the value is not a boolean. Received: ${
          open.value === Missing ? undefined : props.open
        }`
      )
    }

    let dialogState = computed(() =>
      !ready.value ? DialogStates.Closed : open.value ? DialogStates.Open : DialogStates.Closed
    )
    let enabled = computed(() => dialogState.value === DialogStates.Open)

    let hasNestedDialogs = computed(() => nestedDialogCount.value > 1) // 1 is the current dialog
    let hasParentDialog = inject(DialogContext, null) !== null
    let [portals, PortalWrapper] = useNestedPortals()

View on GitHub (pinned to eea57cf46f)

Solutions

  1. Pass the required prop: <Dialog :open="isOpen" @close="isOpen = false">.
  2. If the Dialog is inside a Popover/Disclosure-driven overlay, keep that parent so OpenClosed state is provided.
  3. Check for typos in the prop name (must be exactly `open`).

Example fix

// before
<Dialog @close="() => {}">...</Dialog>

// after
<Dialog :open="isOpen" @close="isOpen = false">...</Dialog>
Defensive patterns

Strategy: validation

Validate before calling

// Vue 3 script setup
const props = defineProps<{ open?: boolean }>()
if (props.open === undefined && !insideOpenClosedProvider) {
  // supply a default before rendering <Dialog>
  props.open = false
}

Type guard

const hasOpenProp = (p: { open?: unknown }): p is { open: boolean } => 'open' in p

Try / catch

Wrap first Dialog render in an ErrorBoundary (React pattern) or guard with v-if="open !== undefined" so the component only mounts with a valid prop.

Prevention

When it happens

Trigger: Rendering <Dialog> with no open prop and no v-model:open; using <Dialog> outside a component that provides open/closed state; misspelling the prop (e.g. :isOpen) so it is never received.

Common situations: Migrating from v1 of Headless UI Vue where `open` was optional; copy-pasting examples that rely on a parent Popover context; forgetting the prop when converting an always-open dialog.

Related errors


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