Offline banner
Appears when the connection drops, and briefly confirms when it is back.
Install
If this is the first component in the project, set up once. This writes ermine.json and copies the theme stylesheet. The installation guide covers Vue, Nuxt and Astro.
npx ermineui initpnpm dlx ermineui inityarn dlx ermineui initbunx ermineui initThen add the component:
npx ermineui add offline-bannerpnpm dlx ermineui add offline-banneryarn dlx ermineui add offline-bannerbunx ermineui add offline-bannerThat copies 2 files into src/components/ui/offline-banner/. Files you already have are kept.
Use
import { OfflineBanner } from '@/components/ui/offline-banner'<OfflineBanner />
<WakeLockToggle v-model="keepAwake" />
<FullscreenButton target="#chart" />
<NotificationPrompt title="Get told when a deployment fails?" description="One notification per failure." @granted="subscribe" />In Nuxt, with the auto-import setting, the tags work in templates without the import. In Astro, use it inside a Vue island.
What gets copied
| File | Where it goes |
|---|---|
OfflineBanner.vue | src/components/ui/offline-banner/OfflineBanner.vue |
index.ts | src/components/ui/offline-banner/index.ts |
The folders are the suggested ones. ermine.json says where yours are.
Source
This is exactly what the command copies. To install by hand, create each file below and the helpers listed above.
OfflineBanner.vue 101 lines
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
const props = withDefaults(
defineProps<{
/** How long the "back online" message stays, in ms. 0 skips it. */
backOnlineFor?: number
}>(),
{ backOnlineFor: 3000 },
)
const emit = defineEmits<{
offline: []
online: []
}>()
// Assumed online until the browser says otherwise: the server cannot know, and most visits are.
const state = ref<'online' | 'offline' | 'back'>('online')
let timer: ReturnType<typeof setTimeout> | undefined
function goOffline() {
clearTimeout(timer)
state.value = 'offline'
emit('offline')
}
function goOnline() {
if (state.value !== 'offline') return
emit('online')
if (props.backOnlineFor <= 0) {
state.value = 'online'
return
}
state.value = 'back'
timer = setTimeout(() => (state.value = 'online'), props.backOnlineFor)
}
onMounted(() => {
// "Offline" here means no network at all. A connection that is up but cannot reach your server still counts as online.
if (!navigator.onLine) goOffline()
window.addEventListener('offline', goOffline)
window.addEventListener('online', goOnline)
})
onBeforeUnmount(() => {
clearTimeout(timer)
window.removeEventListener('offline', goOffline)
window.removeEventListener('online', goOnline)
})
</script>
<template>
<!-- Always in the page, empty while online, so the change is announced when it happens. -->
<div class="er-offline-banner" role="status" :data-state="state">
<template v-if="state === 'offline'">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 3l18 18M8.500 16.500a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.500M19 13a10 10 0 0 0-4.500-2.700M2 9a15 15 0 0 1 4.500-3M22 9a15 15 0 0 0-9-3.900M12 20h.01" />
</svg>
<span><slot>You are offline. Changes will be saved when you reconnect.</slot></span>
</template>
<span v-else-if="state === 'back'"><slot name="online">Back online.</slot></span>
</div>
</template>
<style>
@layer components {
.er-offline-banner {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 1rem;
background: color-mix(in oklab, var(--er-warning) 14%, var(--er-background));
color: var(--er-foreground);
font-size: 0.875rem;
text-align: center;
}
.er-offline-banner[data-state='back'] {
background: color-mix(in oklab, var(--er-success) 14%, var(--er-background));
}
/* Takes no room while there is nothing to say. */
.er-offline-banner[data-state='online'] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
.er-offline-banner svg {
flex-shrink: 0;
width: 1rem;
height: 1rem;
}
}
</style>index.ts 3 lines
/** Offline banner — appears when the connection drops, and briefly confirms when it is back. */
export { default as OfflineBanner } from './OfflineBanner.vue'