Skeleton
Loading placeholder.
Code
Copy into src/components/ph/ .
skeleton.css
.ph-skeleton {
display: block;
background-color: var(--muted);
background-image: linear-gradient(
90deg,
var(--muted) 0px,
color-mix(in oklab, var(--muted), var(--background) 60%) 80px,
var(--muted) 160px
);
background-size: 200% 100%;
animation: ph-shimmer 2.5s linear infinite;
}
@keyframes ph-shimmer {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ph-skeleton {
animation: none;
}
}
Skeleton.astro
---
import "./skeleton.css";
type Props = {
class?: string;
};
const { class: className } = Astro.props;
---
<span aria-hidden="true" class:list={["ph-skeleton", className]}></span>
Copy into components/ph/ (or src/components/ph/).
skeleton.css
.ph-skeleton {
display: block;
background-color: var(--muted);
background-image: linear-gradient(
90deg,
var(--muted) 0px,
color-mix(in oklab, var(--muted), var(--background) 60%) 80px,
var(--muted) 160px
);
background-size: 200% 100%;
animation: ph-shimmer 2.5s linear infinite;
}
@keyframes ph-shimmer {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ph-skeleton {
animation: none;
}
}
skeleton.tsx
import { cn } from "./cn";
import "./skeleton.css";
export function Skeleton({ className }: { className?: string }) {
return <span aria-hidden="true" className={cn("ph-skeleton", className)} />;
}
cn.ts
export const cn = (...parts: (string | false | null | undefined)[]) => parts.filter(Boolean).join(" ");