LoadingOverlay

A loading indicator laid over an area, shown after a short wait.

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>
vue

Usage

import { LoadingOverlay } from '@hina-ui/vue'
ts

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>
vue

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>
vue

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>
vue

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.

Hoshimi Shion running over with a book

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>
vue

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>
vue

Behavior

  • When visible turns true the area stops answering interaction at once and the overlay fades in after delay; when it turns false it fades out and is removed, staying up to minVisible first 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 by text or 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