Progress
LoadingA bar that fills to its value when it scrolls into view, with the number counting up beside it, or a ring with the number in the middle.
Placeholder lines, optionally with an avatar, shimmering while content loads. The shimmer is a gradient texture whose position moves; pure CSS. Marks itself busy for assistive tech.
Live, from the library itself: scroll, hover, navigate away and back. The demo is never gated.
Adjust · props are custom properties, set inline, so you edit the running instance
JavaScript of its own
0 B
CSS 4.8 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
every browser.
One command adds the integration, the base tokens and every component. Then import what you use.
npx astro add moonarcpnpm astro add moonarcbunx astro add moonarc---
import Skeleton from '@moonarc/core/Skeleton';
---
<Skeleton lines={3} avatar />Owns the file, no dependency. The registry item also installs the base tokens.
npx shadcn@latest add https://moonarc.dev/r/skeleton.jsonThe CLI needs a components.json and the @/* alias, which Setup has. The file lands in src/components/moonarc/.
The whole component. Self-contained styles in a cascade layer so your classes always win; it imports nothing.
---
/**
* Skeleton — placeholder blocks with a shimmer, zero JS. The shimmer is a
* gradient texture whose position moves; nothing repaints.
*/
import type { HTMLAttributes } from 'astro/types';
interface Props extends HTMLAttributes<'div'> {
/** Text lines to draw. */
lines?: number;
/** Draw a round avatar beside the lines. */
avatar?: boolean;
/** Seconds per shimmer pass. */
duration?: number;
/** Corner radius of the blocks, any CSS length. */
radius?: string;
}
const { lines = 3, avatar = false, duration = 1.6, radius = '0.375rem', class: className, ...rest } = Astro.props;
---
<div class:list={['ma-skeleton', className]} aria-busy="true" aria-live="polite" data-avatar={avatar ? '' : undefined} style={`--ma-sk-dur:${duration}s;--ma-sk-r:${radius}`} {...rest}>
{avatar && <i class="ma-skeleton__avatar"></i>}
<div class="ma-skeleton__lines">
{Array.from({ length: Math.max(1, lines) }, (_, i) => <i class="ma-skeleton__line" style={`--ma-w:${i === lines - 1 && lines > 1 ? 60 : 100 - (i % 3) * 8}%`}></i>)}
</div>
<slot />
</div>
<style is:global>
@layer components {
:where(.ma-skeleton) {
display: flex;
gap: 0.75rem;
align-items: flex-start;
}
:where(.ma-skeleton__lines) {
flex: 1;
display: grid;
gap: 0.5rem;
}
:where(.ma-skeleton__line),
:where(.ma-skeleton__avatar) {
display: block;
background-color: var(--ma-edge);
background-image: linear-gradient(100deg, transparent 30%, var(--ma-shine) 50%, transparent 70%);
background-size: 200% 100%;
background-position: 150% 0;
animation: ma-skeleton var(--ma-sk-dur, 1.6s) var(--ma-ease-in-out) infinite;
}
:where(.ma-skeleton__line) {
height: 0.8em;
width: var(--ma-w, 100%);
border-radius: var(--ma-sk-r, 0.375rem);
}
:where(.ma-skeleton__avatar) {
flex: none;
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
@media (prefers-reduced-motion: reduce) {
:where(.ma-skeleton__line),
:where(.ma-skeleton__avatar) {
animation: none;
background-image: none;
}
}
}
@keyframes ma-skeleton {
to {
background-position: -50% 0;
}
}
</style>| Prop | Type | Default | Description |
|---|---|---|---|
lines | number | 3 | Text lines; widths vary and the last is short, like real text. |
avatar | boolean | false | A round block beside the lines. |
duration | number | 1.6 | Seconds per shimmer pass. |
radius | string | '0.375rem' | Corner radius of the lines. |
Static blocks, no shimmer.
ClientRouterCSS-only; nothing to rebind.
Replaces: Skeleton (shadcn) · react-loading-skeleton. See the migration table.
A bar that fills to its value when it scrolls into view, with the number counting up beside it, or a ring with the number in the middle.
An indeterminate arc whose length breathes while it rotates, so it looks like it is working rather than spinning a fixed wedge.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown