<script setup lang="ts">
import { ref } from 'vue'
import { Avatar, Button, Card, Inline, Skeleton, Stack, Text } from '@hina-ui/vue'
const loading = ref(true)
</script>
<template>
<Stack class="w-full max-w-sm">
<Card>
<Inline gap="sm" align="start">
<Skeleton :loading="loading" class="rounded-full">
<Avatar size="lg" src="/avatars/selfie.webp" alt="Shion Hoshimi" />
</Skeleton>
<Stack gap="xs" class="min-w-0 flex-1">
<Skeleton :loading="loading">
<Text class="font-medium">Shion Hoshimi</Text>
</Skeleton>
<Skeleton :loading="loading">
<Text tone="muted" size="sm">Translated 128 light novels</Text>
</Skeleton>
</Stack>
</Inline>
</Card>
<Button size="sm" variant="soft" tone="neutral" @click="loading = !loading">
{{ loading ? 'Show the content' : 'Show the skeleton' }}
</Button>
</Stack>
</template>
vue
Usage
import { Skeleton } from '@hina-ui/vue'
ts
Wrap the real content and pass loading. While it is true the content is hidden but still lays itself out, so the placeholder is exactly the size the content will be; once it turns false the wrapper disappears and only the content is left.
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Inline, Skeleton, Stack, Text } from '@hina-ui/vue'
const loading = ref(true)
</script>
<template>
<Stack class="w-full max-w-sm">
<Inline>
<Skeleton :loading="loading">
<Text>ATRI</Text>
</Skeleton>
<Skeleton :loading="loading">
<Text tone="muted" size="sm">Chapter 42</Text>
</Skeleton>
</Inline>
<Button size="sm" variant="soft" tone="neutral" @click="loading = !loading">
{{ loading ? 'Loaded' : 'Loading' }}
</Button>
</Stack>
</template>
vue
With no content of its own, Skeleton is a plain block whose size comes from its classes.
<script setup lang="ts">
import { Skeleton, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack gap="sm" class="w-full max-w-sm">
<Skeleton class="h-4 w-1/3" />
<Skeleton class="h-4 w-full" />
<Skeleton class="h-4 w-4/5" />
</Stack>
</template>
vue
Examples
Shapes
The shape is whatever the classes say: a rounded rectangle for an image, rounded-full for an avatar, a short bar for a line of text.
<script setup lang="ts">
import { Card, Inline, Skeleton, Stack } from '@hina-ui/vue'
</script>
<template>
<Card class="w-full max-w-sm">
<Stack gap="sm">
<Skeleton class="h-32 w-full rounded-lg" />
<Inline gap="sm" align="center">
<Skeleton class="size-10 rounded-full" />
<Stack gap="xs" class="flex-1">
<Skeleton class="h-4 w-1/2" />
<Skeleton class="h-3 w-1/3" />
</Stack>
</Inline>
</Stack>
</Card>
</template>
vue
Behaviour
- A gradient wave sweeps across a fixed fill every 1.2 seconds by default, from left to right. The direction reverses in RTL layouts.
- Under
prefers-reduced-motionthe wave stops and the placeholder stays still. - With
loadingfalse the component renders its slot and nothing else — no wrapper element is left behind.
Accessibility
- The placeholder is
aria-hiddenandinert, so screen readers skip it and nothing inside can take focus. - Announce the loading state at the region that owns it, not on each placeholder.
API
| Prop | Type | Default | Description |
|---|---|---|---|
loading | boolean | true | Whether the placeholder is shown |
as | string | 'span' | The rendered tag |
class | string | — | Classes appended to the placeholder |
| Slot | Description |
|---|---|
default | The real content, which gives the placeholder its size |