Skeleton
A placeholder shown while content is loading.
A placeholder that stands in for content while it loads. Android sweeps a shimmer gradient across it; iOS pulses its opacity. Pick a shape with variant and size it with utility classes.
Installation
npx touchcn add skeleton
Usage
<tcn-skeleton variant="text" class="w-3/4" />
<tcn-skeleton variant="rect" class="h-24" />
<tcn-skeleton variant="circle" class="size-10" />import { TcnSkeleton } from '@/components/ui/skeleton';
<TcnSkeleton variant="text" className="w-3/4" />
<TcnSkeleton variant="rect" className="h-24" />
<TcnSkeleton variant="circle" className="size-10" /><script setup lang="ts">
import { TcnSkeleton } from '@/components/ui/skeleton';
</script>
<template>
<TcnSkeleton variant="text" class="w-3/4" />
<TcnSkeleton variant="rect" class="h-24" />
<TcnSkeleton variant="circle" class="size-10" />
</template>The element is aria-hidden so it stays out of the accessibility tree, and its animation freezes static under prefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
'text' | 'circle' | 'rect' |
'text' |
Placeholder shape. |
Source · Angular
export * from './tcn-skeleton';import { Component, computed, ElementRef, inject, input } from '@angular/core';
import { cn } from '@touchcn/core';
export type TcnSkeletonVariant = 'text' | 'circle' | 'rect';
const variantClass: Record<TcnSkeletonVariant, string> = {
text: 'tcn-skeleton-text',
circle: 'tcn-skeleton-circle',
rect: 'tcn-skeleton-rect',
};
/**
* Placeholder shown while content loads. `variant` picks the shape (a text line,
* a circle for avatars, or a rectangle for media/cards); size it further with
* utility classes. MD sweeps a shimmer gradient across it, iOS pulses its
* opacity — both idiomatic, all in `theme.css`. `aria-hidden` keeps it out of
* the accessibility tree, and `prefers-reduced-motion` freezes it static.
*/
@Component({
selector: 'tcn-skeleton',
template: '',
host: {
'aria-hidden': 'true',
'[class]': 'classes()',
},
})
export class TcnSkeleton {
private readonly authored = inject(ElementRef<HTMLElement>).nativeElement.getAttribute('class') ?? '';
readonly variant = input<TcnSkeletonVariant>('text');
protected readonly classes = computed(() => cn('tcn-skeleton', variantClass[this.variant()], this.authored));
}Source · React
export * from './tcn-skeleton';import { forwardRef } from 'react';
import type { ComponentPropsWithoutRef } from 'react';
import { cn } from '@touchcn/core';
export type TcnSkeletonVariant = 'text' | 'circle' | 'rect';
const variantClass: Record<TcnSkeletonVariant, string> = {
text: 'tcn-skeleton-text',
circle: 'tcn-skeleton-circle',
rect: 'tcn-skeleton-rect',
};
export interface TcnSkeletonProps extends ComponentPropsWithoutRef<'span'> {
variant?: TcnSkeletonVariant;
}
/**
* Placeholder shown while content loads. `variant` picks the shape (a text line,
* a circle for avatars, or a rectangle for media/cards); size it further with
* utility classes. MD sweeps a shimmer gradient across it, iOS pulses its
* opacity — both idiomatic, all in `theme.css`. `aria-hidden` keeps it out of
* the accessibility tree, and `prefers-reduced-motion` freezes it static.
*/
export const TcnSkeleton = forwardRef<HTMLSpanElement, TcnSkeletonProps>(function TcnSkeleton(
{ variant = 'text', className, ...props },
ref,
) {
return <span ref={ref} aria-hidden="true" className={cn('tcn-skeleton', variantClass[variant], className)} {...props} />;
});Source · Vue
<script lang="ts">
export type TcnSkeletonVariant = 'text' | 'circle' | 'rect';
const variantClass: Record<TcnSkeletonVariant, string> = {
text: 'tcn-skeleton-text',
circle: 'tcn-skeleton-circle',
rect: 'tcn-skeleton-rect',
};
</script>
<script setup lang="ts">
import { computed } from 'vue';
/**
* Placeholder shown while content loads. `variant` picks the shape (a text line,
* a circle for avatars, or a rectangle for media/cards); size it further with
* utility classes. MD sweeps a shimmer gradient across it, iOS pulses its
* opacity — both idiomatic, all in `theme.css`. `aria-hidden` keeps it out of
* the accessibility tree, and `prefers-reduced-motion` freezes it static.
*/
const props = withDefaults(defineProps<{ variant?: TcnSkeletonVariant }>(), { variant: 'text' });
const classes = computed(() => ['tcn-skeleton', variantClass[props.variant]]);
</script>
<template>
<span aria-hidden="true" :class="classes" />
</template>export { default as TcnSkeleton } from './TcnSkeleton.vue';
export type { TcnSkeletonVariant } from './TcnSkeleton.vue';