Interactions

useRole

Synchronizes ARIA roles and accessibility relationships for floating surfaces.

useRole manages the semantic accessibility layer for floating surfaces. It synchronizes ARIA roles, aria-expanded, aria-haspopup, and aria-controls on the anchor and floating element.

Type

function useRole(node: FloatingNode, options?: UseRoleOptions): UseRoleReturn;

type FloatingRole = "dialog" | "grid" | "listbox" | "menu" | "menubar" | "tooltip" | "tree";

type FloatingRoleItemRole =
  | "gridcell"
  | "group"
  | "menuitem"
  | "menuitemcheckbox"
  | "menuitemradio"
  | "none"
  | "option"
  | "presentation"
  | "separator"
  | "treeitem";

interface UseRoleOptions {
  enabled?: MaybeRefOrGetter<boolean>;
  role?: MaybeRefOrGetter<FloatingRole | null | undefined>;
  label?: MaybeRefOrGetter<string | null | undefined>;
  labelledBy?: MaybeRefOrGetter<string | null | undefined>;
  describedBy?: MaybeRefOrGetter<string | null | undefined>;
  controls?: MaybeRefOrGetter<boolean>;
  modal?: MaybeRefOrGetter<boolean>;
  listRef?: Ref<Array<HTMLElement | null>>;
  itemRole?:
    | MaybeRefOrGetter<FloatingRoleItemRole | null | undefined>
    | ((index: number, itemEl: HTMLElement) => FloatingRoleItemRole | null | undefined);
  disabledIndices?: Array<number> | ((index: number) => boolean);
  checkedIndices?: Array<number> | ((index: number) => boolean);
  selectedIndices?: Array<number> | ((index: number) => boolean);
}

interface UseRoleReturn {
  cleanup: () => void;
}

Options

NameTypeDefaultNotes
enabledMaybeRefOrGetter<boolean>trueReactive toggle. While false, attributes are not written.
roleMaybeRefOrGetter<FloatingRole | null>nullSurface role. Semantic attributes are applied only when a role is set.
labelMaybeRefOrGetter<string | null>undefinedSets aria-label on the floating surface.
labelledByMaybeRefOrGetter<string | null>undefinedSets aria-labelledby referencing an element id.
describedByMaybeRefOrGetter<string | null>undefinedSets aria-describedby referencing an element id.
controlsMaybeRefOrGetter<boolean>trueWhen true, links anchor to panel with aria-controls.
modalMaybeRefOrGetter<boolean>undefinedWith role: "dialog", writes aria-modal="true".
listRefRef<Array<HTMLElement | null>>undefinedItem refs array for automatic item role application.
itemRoleRole string or functionAutoFixed or per-index role for items in listRef.
disabledIndicesArray<number> | ((idx) => boolean)undefinedMarks items with aria-disabled="true".
checkedIndicesArray<number> | ((idx) => boolean)undefinedMarks items with aria-checked="true".
selectedIndicesArray<number> | ((idx) => boolean)undefinedMarks items with aria-selected="true".

Returns

NameTypeNotes
cleanup() => voidRestores original attributes on all elements and stops attribute watchers.

Details

Role Mapping Behavior

  • role: "tooltip": Sets role="tooltip" on the floating element and adds aria-describedby pointing to the tooltip ID on the anchor while open. Tooltips are not treated as popups.
  • Popup Roles ("menu", "listbox", "tree", "grid", "dialog"): Sets aria-haspopup="{role}", aria-expanded="true|false", and aria-controls="{id}" on the anchor element.
  • ID Generation: Automatically generates unique IDs (vfloat-{role}-{id}) if elements do not already possess IDs.
  • Submenus: Calling useRole(childNode, { role: "menu" }) on a child node writes submenu attributes (aria-haspopup="menu", aria-expanded) onto the child anchor element inside the parent menu.

Responsibility Boundaries

useRole manages accessibility attributes. It intentionally does not attach keyboard listeners, manage tabindex, or trap focus:

Example

<script setup lang="ts">
import { ref } from "vue";
import {
  useClick,
  useEscapeKey,
  useFloatingNode,
  useOutsideClick,
  usePosition,
  useRole,
} from "v-float";

const anchorEl = ref<HTMLElement | null>(null);
const floatingEl = ref<HTMLElement | null>(null);
const itemsRef = ref<Array<HTMLElement | null>>([]);
const items = ["Profile", "Settings", "Billing"];

const node = useFloatingNode({ anchorEl, floatingEl });
usePosition(node);

useClick(node);
useOutsideClick(node);
useEscapeKey(node);
useRole(node, {
  role: "menu",
  label: "User Menu",
  listRef: itemsRef,
});
</script>

<template>
  <button ref="anchorEl" type="button">User Menu</button>

  <div v-if="node.open" ref="floatingEl">
    <div
      v-for="(item, idx) in items"
      :key="item"
      :ref="(el) => (itemsRef[idx] = el as HTMLElement | null)"
    >
      {{ item }}
    </div>
  </div>
</template>

See Also

Released under the MIT License. Copyright © 2026