tailwindlabs/headlessui · warning

Invalid role [${role}] passed to <Dialog />. Only `dialog` a

Error message

Invalid role [${role}] passed to <Dialog />. Only `dialog` and and `alertdialog` are supported. Using `dialog` instead.

What it means

This console.warn comes from the Headless UI Vue <Dialog /> component when the `role` prop is set to anything other than 'dialog' or 'alertdialog'. The component falls back to role="dialog" but warns once (a module-level didWarnOnRole flag ensures it only fires a single time). It exists to catch invalid WAI-ARIA dialog roles, since only those two roles are meaningful for a modal dialog.

Source

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

    id: { type: String, default: () => `headlessui-dialog-${useId()}` },
    role: { type: String as PropType<'dialog' | 'alertdialog'>, default: 'dialog' },
  },
  emits: { close: (_close: boolean) => true },
  setup(props, { emit, attrs, slots, expose }) {
    let ready = ref(false)
    onMounted(() => {
      ready.value = true
    })

    let didWarnOnRole = false
    let role = computed(() => {
      if (props.role === 'dialog' || props.role === 'alertdialog') {
        return props.role
      }

      if (!didWarnOnRole) {
        didWarnOnRole = true
        console.warn(
          `Invalid role [${role}] passed to <Dialog />. Only \`dialog\` and and \`alertdialog\` are supported. Using \`dialog\` instead.`
        )
      }

      return 'dialog'
    })

    let nestedDialogCount = ref(0)

    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)

View on GitHub (pinned to eea57cf46f)

Solutions

  1. Set role to a valid value: <Dialog role="alertdialog"> for destructive/confirmation modals, or omit the prop entirely (defaults to 'dialog').
  2. If the role is dynamic, ensure the expression is typed as `'dialog' | 'alertdialog'` and has a default: `role="alertdialog"` fallback.
  3. Check for typos and casing — the comparison is case-sensitive against the exact strings 'dialog' and 'alertdialog'.

Example fix

// before
<Dialog role="modal" @close="close">
<!-- warns: Invalid role [modal] -->

// after
<Dialog role="dialog" @close="close">

<!-- or for confirmation dialogs -->
<Dialog role="alertdialog" @close="close">
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_ROLES = ['dialog', 'alertdialog'] as const
const role = computed(() =>
  VALID_ROLES.includes(props.modalRole) ? props.modalRole : 'alertdialog'
)

Type guard

type DialogRole = 'dialog' | 'alertdialog'
function isValidDialogRole(role: unknown): role is DialogRole {
  return role === 'dialog' || role === 'alertdialog'
}

Prevention

When it happens

Trigger: Passing a non-standard or misspelled role to <Dialog role="...">, e.g. role="tooltip", role="dialogue", role="Dialog" (case-sensitive), or binding a variable that resolves to undefined/empty string at render time.

Common situations: Dynamically computing the role from a prop or config object that can be undefined; copy-pasting code that used a different role string; upgrading from a version that silently accepted any role; passing a role intended for a different component (e.g. using <Dialog> where <Disclosure> or a custom overlay was meant).

Related errors


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