Offline banner
A non-blocking banner that appears when the browser loses the network, and confirms when it comes back.
<script setup lang="ts">
import OfflineBanner from '@shared/components/common/OfflineBanner.vue'
import { useConnectivity } from '@shared/composables/useConnectivity'
const { online, status } = useConnectivity()
</script>
<template>
<div class="space-y-2">
<OfflineBanner />
<p class="text-sm">
Browser reports: <strong>{{ online ? 'online' : 'offline' }}</strong>
</p>
<p class="text-sm text-muted-foreground">
Banner status: {{ status }}. Switch your network off to see the banner
appear at the top of the window.
</p>
</div>
</template>TIP
The banner renders nothing while you are online. Switch your network off, or use your browser's offline mode, and it slides in at the top of the window.
Usage
It takes no props. Mount it once, near the root, and it looks after itself. In the template it lives in App.vue, which is what puts it on the sign-in page too, and a sign-in form is exactly where a reader needs telling that the network has gone.
It is deliberately non-blocking. Losing the network does not stop you reading the page you are already on, so it does not cover it.
The three states
| Status | When | What shows |
|---|---|---|
idle | Online, and nothing has gone wrong | Nothing. |
offline | The browser reports no network | An amber banner: "You're offline", with a Details link to /offline. |
reconnected | For 2600ms after the network returns | An emerald banner: "Back online". |
The reconnected state is held for those 2600 milliseconds on purpose. A banner that vanishes the instant the network returns leaves the reader unsure whether anything happened.
The Details link only appears while offline. Once reconnected, the page is the thing to look at, not a link away from it.
The state comes from useConnectivity (src/shared/composables/useConnectivity.ts), a module-scope singleton, so the banner and the /offline page cannot disagree about what is going on.
navigator.onLine reports whether the browser has a network, not whether that network reaches anything. A captive portal still reads as online, which is why the copy says you appear to be offline rather than asserting it.
The banner hides itself inside the responsive preview frame, so previewing a page does not stack a second banner over the first.
API
Source: src/shared/components/common/OfflineBanner.vue
Props
None.
Emits
None.
Slots
None.
Accessibility
- The banner is
role="status"witharia-live="polite", so the change is announced without interrupting what the reader is doing. - The state is carried by an icon, a heading and the body text, not by color alone.
- It never takes focus, so it cannot pull the reader out of a form they are in the middle of.
- It carries
sa-no-print, so it never appears on a printed page. - Its position uses logical properties, so it stays centered in a right-to-left layout.