@nuxt/docs-nightly 5.0.0-29753941.df18c4a8 → 5.0.0-29754172.7cdbbb7a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ ---
2
+ navigation.title: 'Accessibility'
3
+ title: Nuxt accessibility
4
+ description: Best practices for accessibility in Nuxt apps.
5
+ ---
6
+
7
+ Most of what makes an app accessible is not specific to Nuxt: color contrast, form semantics, and ARIA work the same here as in any Vue or plain HTML application, and the [resources](#useful-resources) at the end of this guide cover them well.
8
+
9
+ What Nuxt does change is navigation. Once your app has hydrated, it routes on the client, so the document is never reloaded and the browser no longer announces a new page or resets focus for you. Nuxt ships with features that fill some of that gap, and a few conventions cover the rest. This guide outlines best practices for handling that.
10
+
11
+ ::tip
12
+ [`@nuxt/a11y`](https://github.com/nuxt/a11y) surfaces accessibility problems in your components while you develop, alongside the practices below. It is in alpha, so expect its API to change.
13
+ ::
14
+
15
+ ## Route Announcements
16
+
17
+ Screen readers announce a full page load by themselves, but they have no way of knowing that a client-side navigation happened. [`<NuxtRouteAnnouncer>`](/docs/4.x/api/components/nuxt-route-announcer) solves this by rendering a hidden live region and writing the new page title into it after every navigation:
18
+
19
+ ```vue [app.vue]
20
+ <template>
21
+ <NuxtRouteAnnouncer />
22
+ <NuxtPage />
23
+ </template>
24
+ ```
25
+
26
+ The announcer reads the title that Unhead rendered, so it is only as useful as your titles are. If two routes share the same `<title>`, users hear nothing on the way between them.
27
+
28
+ When you need to announce something else, or to change how urgently it is announced, use [`useRouteAnnouncer`](/docs/4.x/api/composables/use-route-announcer):
29
+
30
+ ```vue [app/pages/search.vue]
31
+ <script setup lang="ts">
32
+ const { set } = useRouteAnnouncer()
33
+ const { data: results } = await useFetch('/api/search')
34
+
35
+ watch(results, (results) => {
36
+ set(`${results?.length ?? 0} results found`)
37
+ })
38
+ </script>
39
+ ```
40
+
41
+ :read-more{title="NuxtRouteAnnouncer" to="/docs/4.x/api/components/nuxt-route-announcer"}
42
+
43
+ For in-page updates that are not navigations, such as form validation or toasts, use [`<NuxtAnnouncer>`](/docs/4.x/api/components/nuxt-announcer) with [`useAnnouncer`](/docs/4.x/api/composables/use-announcer) instead.
44
+
45
+ ## Page Titles
46
+
47
+ Because the route announcer follows the document title, giving every route a distinct title is the single most valuable thing you can do. Set a global template in `app.vue` and let each page fill in its own part:
48
+
49
+ ```vue [app.vue]
50
+ <script setup lang="ts">
51
+ useHead({
52
+ titleTemplate: title => title ? `${title} - Nuxt` : 'Nuxt',
53
+ })
54
+ </script>
55
+ ```
56
+
57
+ ```vue [app/pages/about.vue]
58
+ <script setup lang="ts">
59
+ useHead({
60
+ title: 'About us',
61
+ })
62
+ </script>
63
+ ```
64
+
65
+ If your titles come from route metadata rather than from the page itself, you can read [`definePageMeta`](/docs/4.x/directory-structure/app/pages#page-metadata) values from [`useRoute`](/docs/4.x/api/composables/use-route) in a layout.
66
+
67
+ :read-more{title="SEO and Meta" to="/docs/4.x/getting-started/seo-meta#dynamic-title"}
68
+
69
+ ## Links
70
+
71
+ Use [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) for in-app navigation. It renders a real `<a href="...">`, which means it is focusable, appears in the tab order, and works with middle-click and "open in new tab", all of which you would have to reimplement on a `<div>` with a `@click` handler calling `navigateTo`.
72
+
73
+ ```vue
74
+ <template>
75
+ <NuxtLink to="/about">About page</NuxtLink>
76
+ </template>
77
+ ```
78
+
79
+ In a menu or a set of breadcrumbs, the link matching the current route already exposes `aria-current="page"`, so assistive technology can tell which item you are on. Where a different token describes the relationship better, such as a step in a multi-page form, set [`ariaCurrentValue`](/docs/4.x/api/components/nuxt-link#routerlink):
80
+
81
+ ```vue
82
+ <template>
83
+ <NuxtLink
84
+ to="/checkout/payment"
85
+ aria-current-value="step"
86
+ >Payment</NuxtLink>
87
+ </template>
88
+ ```
89
+
90
+ Links to files in your `public/` directory, or to another app on the same origin, are not routes that Vue Router knows about. Mark them as [`external`](/docs/4.x/api/components/nuxt-link#handling-static-file-and-cross-app-links) so the browser performs a real navigation instead of failing to match a route.
91
+
92
+ :read-more{title="NuxtLink" to="/docs/4.x/api/components/nuxt-link"}
93
+
94
+ ## Focus Management
95
+
96
+ After a client-side navigation, focus stays where it was, which is usually the link the user just activated. Vue Router does not move it and neither does Nuxt, so a keyboard user can end up tabbing through the whole header again to reach the content that just changed.
97
+
98
+ A skip link as the first tab stop of your app is the conventional fix, and it helps on the initial page load too:
99
+
100
+ ```vue [app.vue]
101
+ <template>
102
+ <a
103
+ class="skip-link"
104
+ href="#main"
105
+ >Skip to main content</a>
106
+ <AppHeader />
107
+ <main
108
+ id="main"
109
+ tabindex="-1"
110
+ >
111
+ <NuxtPage />
112
+ </main>
113
+ </template>
114
+
115
+ <style>
116
+ .skip-link {
117
+ position: absolute;
118
+ left: -9999px;
119
+ }
120
+ .skip-link:focus {
121
+ position: static;
122
+ }
123
+ </style>
124
+ ```
125
+
126
+ `<main>` is not focusable on its own, so it needs `tabindex="-1"` to accept focus from the skip link or from a script. Use `-1` rather than a positive value, which would move the element in the tab order and surprise everyone else.
127
+
128
+ If it suits your app, you can go further and move focus to the main region after every navigation from a plugin:
129
+
130
+ ```ts [app/plugins/focus-main.client.ts]
131
+ export default defineNuxtPlugin(() => {
132
+ useRouter().afterEach((to, from) => {
133
+ if (to.path === from.path) {
134
+ return
135
+ }
136
+ nextTick(() => document.getElementById('main')?.focus())
137
+ })
138
+ })
139
+ ```
140
+
141
+ ::tip
142
+ Navigate around your app with the keyboard alone. Tabbing from the skip link into `<main>` after a couple of navigations will surface most focus problems quickly.
143
+ ::
144
+
145
+ ## Scroll Behavior
146
+
147
+ Nuxt scrolls to the top on a new route, restores the previous position when the user goes back, and scrolls to hash targets. If you need something different, such as smooth scrolling or a different offset, configure [`scrollBehaviorType`](/docs/4.x/guide/recipes/custom-routing#scroll-behavior-for-hash-links) or write your own `scrollBehavior` in [`router.options.ts`](/docs/4.x/guide/recipes/custom-routing#router-options). Bear in mind that smooth scrolling should respect the user's `prefers-reduced-motion` setting.
148
+
149
+ :read-more{title="Custom routing" to="/docs/4.x/guide/recipes/custom-routing"}
150
+
151
+ ## Useful Resources
152
+
153
+ - [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
154
+ - [MDN: Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
155
+ - [Understanding WCAG 2.1](https://www.w3.org/WAI/WCAG21/Understanding/)
@@ -13,6 +13,8 @@ links:
13
13
  This component is available in Nuxt v3.12+.
14
14
  ::
15
15
 
16
+ :read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#route-announcements"}
17
+
16
18
  ## Usage
17
19
 
18
20
  Add `<NuxtRouteAnnouncer/>` in your [`app.vue`](/docs/4.x/directory-structure/app/app) or [`app/layouts/`](/docs/4.x/directory-structure/app/layouts) to enhance accessibility by informing assistive technologies about page title changes. This ensures that navigational changes are announced to users relying on screen readers.
@@ -12,6 +12,8 @@ links:
12
12
  `<NuxtLink>` is a drop-in replacement for both Vue Router's `<RouterLink>` component and HTML's `<a>` tag. It intelligently determines whether the link is _internal_ or _external_ and renders it accordingly with available optimizations (prefetching, default attributes, etc.)
13
13
  ::
14
14
 
15
+ :read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#links"}
16
+
15
17
  ## Internal Routing
16
18
 
17
19
  In this example, we use `<NuxtLink>` component to link to another page of the application.
@@ -18,6 +18,8 @@ This composable is available in Nuxt v3.12+.
18
18
  A composable which observes the page title changes and updates the announcer message accordingly. Used by [`<NuxtRouteAnnouncer>`](/docs/4.x/api/components/nuxt-route-announcer) and controllable.
19
19
  It hooks into Unhead's `dom:rendered` hook to read the page's title and set it as the announcer message.
20
20
 
21
+ :read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#route-announcements"}
22
+
21
23
  ## Parameters
22
24
 
23
25
  - `politeness`: Sets the urgency for screen reader announcements: `off` (disable the announcement), `polite` (waits for silence), or `assertive` (interrupts immediately). (default `polite`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/docs-nightly",
3
- "version": "5.0.0-29753941.df18c4a8",
3
+ "version": "5.0.0-29754172.7cdbbb7a",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/nuxt/nuxt.git",