最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - dialog with role='dialog' causing screen reader to announce all content despite setting focus on sp

programmeradmin6浏览0评论

I'm implementing an accessible profile menu in Vue 3 using v-menu with role="dialog". When the menu opens, I'm setting focus on a specific element, but the screen reader (like NVDA) is announcing all content in the dialog instead of just the focused element.

<div class="d-sm-flex d-md-flex justify-end align-center menu-items">
                <v-menu v-model="menu" :close-on-content-click="false" location="bottom"
                  class="profile-card" @update:modelValue="handleMenuToggle">
                  <template v-slot:activator="{ props }">
                    <v-btn color="indigo" v-bind="props" class="profile-btn" role="button" id="profile-menu-button"
                      @click="openProfileMenu" aria-labelledby="profileLabel userName userRoleText"
                      :aria-expanded="menu" aria-haspopup="dialog" ref="profileButton">
                      <img src="/images/user.svg" alt="" aria-hidden="true" />
                      <div class="d-none d-sm-flex flex-column align-start">
                        <span id="profileLabel" class="sr-only">{{ t('profile') }}</span>
                        <span class="font-16 font-700 color-white" id="userName">{{ store?.user?.name }}</span>
                        <span class="font-14 font-400 color-white" id="userRoleText">{{ userRole }}</span>
                      </div>
                    </v-btn>
                  </template>
                  <v-card min-width="380" role="dialog" aria-modal="true" aria-label="Profile Mega Menu" ref="menuCard"
                    @keydown.escape="closeProfileMenu">
                    <div class="header text-right pa-6 pr-8 py-0">
                      <button id="close-profile-menu" @click="closeProfileMenu" :aria-label="ariaLabelData.closeProfile"
                        class="cursor-pointer close-icon" ref="closeButton">
                        <img src="/images/close-icon.svg" alt="" aria-hidden="true" />
                      </button>
                    </div>
                    <div class="uesrname" aria-labelledby="profileLabelInner userNameInner userRoleTextInner"
                      tabindex="0">
                      <div class="user-profile">
                        <span id="profileLabelInner" class="sr-only">{{ t('profile') }}</span>
                        <p id="userNameInner" class="font-16 font-600 name">{{ store?.user?.fullName }}</p>
                        <p id="userRoleTextInner" class="font-12 font-400 role">{{ userRole }}</p>
                      </div>
                    </div>
                    <div class="profileMenu">
                      <div 
                       
                        @click="closeMenu" 
                        :aria-label="ariaLabelData.myProfile" 
                        tabindex="0"
                        id="my-profile-button" 
                        ref="myProfileButton"
                        class="profileMenu__item">
                        <img src="/public/images/user-blue.svg" class="mr-4" alt="" aria-hidden="true" />
                        <span aria-hidden="true">{{ t('myProfile') }}</span>
                      </div>
                    
                      <div
                        v-if="store?.user?.roles.includes('PARTNER_ADMIN') || store?.user?.roles.includes('ADMIN') || store?.user?.roles.includes('SUB_ADMIN')"
                        @click="redirectToDashboard" 
                        class="profileMenu__item profileMenu__item--dashboard" 
                        :aria-label="ariaLabelData.dashboard"
                        tabindex="0">
                        <div class="profileMenu__item-wrapper">
                          <div class="profileMenu__item-primary">
                            <img src="/public/images/dashboard.svg" class="mr-4" alt="" aria-hidden="true" />
                            <span aria-hidden="true">{{ t('dashboard') }}</span>
                          </div>
                          <div class="profileMenu__item-secondary">
                            <img src="/public/images/action-icon.svg" alt="" aria-hidden="true" />
                          </div>
                        </div>
                      </div>
                    
                      <div
                        v-if="(store?.user?.roles.includes('PARTNER_ADMIN') || store?.user?.roles.includes('ADMIN')) && store?.user?.profileConfiguration?.enabled"
                        @click="redirectToProfile" 
                        class="profileMenu__item profileMenu__item--profile" 
                        :aria-label="ariaLabelData.profilePage"
                        tabindex="0">
                        <div class="profileMenu__item-wrapper">
                          <div class="profileMenu__item-primary">
                            <img src="/public/images/corporate-icon.svg" class="mr-4" alt="" aria-hidden="true" />
                            <span aria-hidden="true">{{ t('profilePage') }}</span>
                          </div>
                          <div class="profileMenu__item-secondary">
                            <img src="/public/images/action-icon.svg" alt="" aria-hidden="true" />
                          </div>
                        </div>
                      </div>
                    
                      <div
                        @click="closeMenu" 
                        class="profileMenu__item" 
                        :aria-label="ariaLabelData.mfasettings"
                        tabindex="0">
                        <img src="/public/images/mfa-setting.svg" class="mr-4" alt="" aria-hidden="true" />
                        <span aria-hidden="true">{{ t('mfasettings') }}</span>
                      </div>
                    </div>
                    <v-card-actions>
                      <v-btn variant="text" @click="signout" :aria-label="ariaLabelData.signout" role="button"
                        tabindex="0">
                        {{ t('signout') }}
                        <img src="/public/images/arrow-blue-right.svg" class="ml-2" alt="" aria-hidden="true" />
                      </v-btn>
                    </v-card-actions>
                  </v-card>
                </v-menu>
              </div>

Expected behavior: When the dialog opens and focus is set on the my profile button, only that button should be announced by the screen reader.

Actual behavior: The screen reader announces all focusable elements within the dialog, even though focus is properly trapped and set on the my profile button.

What I've tried:

  • Setting aria-modal="true"
  • Using trapFocus() to contain focus within the dialog
  • Setting explicit focus on the my profile button with a delay

Is there something specific about Vue's rendering or the way screen readers interact with dynamically created dialogs that I'm missing?

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论