Skip to content

Keyboard Navigation

Single, dismiss-only action

As mentioned in Built-in Notifications / Accessibility, built-in notifications cannot be focused using Tab.

If you have custom components with only one dismissal action and specific duration, you might want to apply the same logic and set its tabindex to -1.

Multiple actions

For custom notifications with multiple actions, proper keyboard navigation should be enabled for all users. These notifications typically have a duration set to Infinity, preventing automatic dismissal.

Notivue provides a hassle-free component, enabling proper keyboard navigation and tab management for custom components with multiple actions.

This seamlessly combines with configuration options like queue, limit and pauseOnTabChange and with NotivueSwipe. It works with any duration setting.

💡 Test this feature on the demo website by pushing some Headless - Actions notifications and then pressing Tab after the first one is displayed.

1. Configure the stream

Wrap Notivue with NotivueKeyboard and pass the exposed containersTabIndex prop:

vue
<script setup>
import { NotivueKeyboard } from 'notivue'

import NotificationWithActions from './NotificationWithMultipleActions.vue'
</script>

<template>
  <NotivueKeyboard v-slot="{ containersTabIndex }">
    <Notivue v-slot="item" :containersTabIndex="containersTabIndex">
      <NotificationWithActions :item="item" />
    </Notivue>
  </NotivueKeyboard>

  <RouterView />
</template>

containersTabIndex is a reactive object that dinamically toggles tab focusing for notifications with two or more focusable elements and disables it for any others that dont't meet the criteria.

2. Configure your component

The last step is to perform the same operation to any focusable elements inside your notification using useNotivueKeyboard and style them accordingly.

vue
<script setup lang="ts">
// ...

import { useNotivueKeyboard } from 'notivue'

const { elementsTabIndex } = useNotivueKeyboard() 
</script>

<template>
  <div class="Notification">
    <p :role="item.ariaRole" :aria-live="item.ariaLive">
      {{ item.message }}
    </p>
    <nav>
      <button @click="someCallback" :tabIndex="elementsTabIndex">Deny</button>
      <button @click="someCallback" :tabIndex="elementsTabIndex">Accept</button>
    </nav>
  </div>
</template>

<style scoped>
[data-notivue-container]:focus-visible .Notification {
  outline: 4px solid royalblue;
}

button:focus-visible {
  outline: 2px solid royalblue;
  outline-offset: 2px;
}
</style>

Then, somewhere in your global CSS, remove the outline from the container:

css
[data-notivue-container]:focus-visible {
  outline: none;
}

1. Entering the stream

NotivueKeyboard observes the stream and looks for notifications that have 2 or more focusable elements and automatically flags them as candidates for keyboard navigation.

If the user is not navigating the stream, as soon as a new candidate is pushed, it can be focused by just pressing Tab.

If the user is already navigating, the focus is automatically moved to the new candidate (at the top of the stream).


2. Leaving the stream

Users can exit the stream by either:

  • Pressing Shift + Tab on the first notification
  • Pressing Tab on the last focusable element
  • Pressing CTRL+N
  • Pressing Escape
  • Clicking with the mouse outside the stream

In any of the above scenarios, users are notified with a push.info() notification that the stream can be accessed again using CTRL+N.


3. Re-entering the stream

  • If new notifications are available, pressing Tab (or CRTL+N) is enough to re-enter the stream.
  • If no new notifications are available, but the previous ones are still displayed, users can re-enter the stream by pressing CTRL+N.
  • If the stream is empty and users attempt to enter it using CTRL+N, they'll be notified accordingly with another push.info notification.

Touch Devices

The above mentioned flow is designed for keyboard interaction and won't have any effect nor interfere with touch devices.

Simply remember to set their duration to Infinity and let users engage naturally.

Customization - Props

You can customize the announcements and define a define a different key to be used in conjuction with CTRL:

vue
<script setup>
const leaveMessage =
  "You're leaving the notifications stream. Press CTRL+N to navigate it again."
const emptyMessage = 'No notifications to navigate.'
</script>

<template>
  <NotivueKeyboard
    comboKey="N"
    :handleClicks="true"
    :leaveMessage="leaveMessage"
    :emptyMessage="emptyMessage"
    :renderAnnouncement="true"
    :maxAnnouncements="3"
  >
    <!-- ... -->
  </NotivueKeyboard>
</template>
  • handleClicks is enabled by default and handles the behavior after the user presses with Space/Enter a link or a button (assuming that it also dismisses the notification).

    If there's a next notification available, it will focus its container, if not it will exit the stream and announce the leaveMessage.

  • renderAnnouncement is enabled by default and renders the announcement as a push.info() notification. If set to false, the announcement will only be made available to screen readers.

Check the API Reference for more details.

Customization - Announcement component

When announcements are made, NotivueKeyboard pushes an info notification to the stream.

To use a dedicated component for those notifications, you can do so by leveraging isNotivueKeyboard push prop.

vue
<script setup>
import CustomAnnouncement from './CustomAnnouncement.vue'
import MyCustomNotification from './MyCustomNotification.vue'
</script>

<template>
  <NotivueKeyboard v-slot="{ containersTabIndex }">
    <Notivue v-slot="item" :containersTabIndex="containersTabIndex">
      <CustomAnnouncement v-if="item.props.isNotivueKeyboard" :item="item" />

      <MyCustomNotification :item="item" v-else />
    </Notivue>
  </NotivueKeyboard>

  <!-- ... -->
</template>