This week
128 bookmarks, 32 comments, 12 new followers.
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Card, LoadingOverlay, Stack, Text } from '@hina-ui/vue'
const loading = ref(false)
async function reload() {
loading.value = true
await new Promise(resolve => setTimeout(resolve, 1800))
loading.value = false
}
</script>
<template>
<Stack gap="sm" align="start">
<Card class="relative w-96">
<Stack gap="xs">
<Text weight="medium">This week</Text>
<Text tone="muted" size="sm">128 bookmarks, 32 comments, 12 new followers.</Text>
</Stack>
<LoadingOverlay :visible="loading" text="Refreshing" />
</Card>
<Button variant="outline" tone="neutral" :disabled="loading" @click="reload">Refresh</Button>
</Stack>
</template>
Usage
import { LoadingOverlay } from '@hina-ui/vue'
The loading overlay fills the nearest positioned container, dims the content and puts a loading indicator on top; place it inside a container with relative and drive it with visible. It blocks interaction underneath as soon as it appears, but only becomes visible after a short wait, so a request that finishes quickly never flashes it.
This content is dimmed and cannot be clicked while loading.
<script setup lang="ts">
import { ref } from 'vue'
import { Card, LoadingOverlay, Stack, Switch, Text } from '@hina-ui/vue'
const loading = ref(true)
</script>
<template>
<Stack gap="md" align="start">
<Switch v-model="loading">Show the overlay</Switch>
<Card class="relative w-96">
<Text>This content is dimmed and cannot be clicked while loading.</Text>
<LoadingOverlay :visible="loading" />
</Card>
</Stack>
</template>
Examples
Text
text shows a line under the indicator and doubles as the indicator’s accessible name.
Syncing the draft to the cloud.
<script setup lang="ts">
import { Card, LoadingOverlay, Text } from '@hina-ui/vue'
</script>
<template>
<Card class="relative h-40 w-96">
<Text>Syncing the draft to the cloud.</Text>
<LoadingOverlay visible text="Syncing" size="lg" />
</Card>
</template>
Delay and minimum stay
delay is how long the overlay waits after visible turns true before fading in, 300 milliseconds by default; taken away within that time, nothing shows. Set it to 0 to show at once, which suits actions the user started knowingly. minVisible is how long it stays once shown, 300 milliseconds by default, so it does not flash; set it to 0 to fade out the moment the request ends.
With the default delay the fast request never shows the overlay; with "Show at once" both do.
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Card, Inline, LoadingOverlay, Stack, Switch, Text } from '@hina-ui/vue'
const loading = ref(false)
const immediate = ref(false)
const result = ref('')
async function request(ms: number) {
loading.value = true
result.value = ''
await new Promise(resolve => setTimeout(resolve, ms))
loading.value = false
result.value = `Returned after ${ms} ms`
}
</script>
<template>
<Stack gap="md" align="start">
<Inline gap="md" align="center">
<Button variant="outline" tone="neutral" :disabled="loading" @click="request(150)">
Fast request
</Button>
<Button variant="outline" tone="neutral" :disabled="loading" @click="request(1500)">
Slow request
</Button>
<Switch v-model="immediate">Show at once</Switch>
</Inline>
<Card class="relative w-96">
<Text>
{{
result ||
'With the default delay the fast request never shows the overlay; with "Show at once" both do.'
}}
</Text>
<LoadingOverlay :visible="loading" :delay="immediate ? 0 : undefined" />
</Card>
</Stack>
</template>
Custom content
The default slot replaces the indicator and text inside the veil, for a brand figure, a progress note or anything of your own; the veil, the delay and the blocking of interaction stay the same. Custom content should carry role="status" or equivalent text so assistive technology knows there is a wait.
The shelf is being tidied.

Shion is tidying the shelf, one moment.
<script setup lang="ts">
import { Card, Image, LoadingOverlay, Stack, Text } from '@hina-ui/vue'
</script>
<template>
<Card class="relative h-56 w-96">
<Text>The shelf is being tidied.</Text>
<LoadingOverlay visible :delay="0">
<Stack gap="xs" align="center" role="status">
<Image
src="/mascot/run.gif"
alt="Hoshimi Shion running over with a book"
:ratio="1"
fit="contain"
:skeleton="false"
eager
:lazy="false"
class="size-28"
/>
<Text size="sm" tone="muted">Shion is tidying the shelf, one moment.</Text>
</Stack>
</LoadingOverlay>
</Card>
</template>
Covering the viewport
fixed makes the overlay cover the whole viewport for page-level waits.
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Image, LoadingOverlay, Stack, Text } from '@hina-ui/vue'
const loading = ref(false)
async function reload() {
loading.value = true
await new Promise(resolve => setTimeout(resolve, 2000))
loading.value = false
}
</script>
<template>
<Button variant="outline" tone="neutral" @click="reload">Cover the page for two seconds</Button>
<LoadingOverlay :visible="loading" fixed>
<Stack gap="xs" align="center" role="status">
<Image
src="/mascot/run.gif"
alt="Hoshimi Shion running over with a book"
:ratio="1"
fit="contain"
:skeleton="false"
eager
:lazy="false"
class="size-32"
/>
<Text size="sm" tone="muted">Switching account</Text>
</Stack>
</LoadingOverlay>
</template>
Behavior
- When
visibleturns true the area stops answering interaction at once and the overlay fades in afterdelay; when it turns false it fades out and is removed, staying up tominVisiblefirst if it appeared only just now. - Taken away within the wait, it never flashes.
- The overlay neither stops the page from scrolling nor takes focus; to interrupt a whole task use the lock of a Dialog.
Accessibility
- The indicator is
role="status", named bytextor by the locale’s “loading” when there is none. - The visible text is hidden from assistive technology so it is not announced twice.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | false | Whether the overlay shows |
text | string | — | Line under the indicator |
fixed | boolean | false | Whether it covers the whole viewport |
size | 'sm' | 'md' | 'lg' | 'md' | Size of the indicator |
delay | number | 300 | Milliseconds to wait before showing; 0 shows at once |
minVisible | number | 300 | Milliseconds it stays at least once shown |
class | string | — | Classes appended to the overlay |
Slots
| Slot | Description |
|---|---|
| default | Content inside the veil; the indicator and text by default |