Skeleton Card
A loading placeholder for a card, with a light sweep moving across it.
Preview and code
index.html
<div class="sk" aria-busy="true" aria-label="Loading card"><div class="sk__img"></div><div class="sk__row"><span class="sk__av"></span><span class="sk__l sk__l--w"></span></div><span class="sk__l"></span><span class="sk__l sk__l--s"></span></div>
styles.css
.sk{position:relative;overflow:hidden;width:280px;padding:16px;border:1px solid var(--line);border-radius:16px;display:grid;gap:12px}
.sk>*,.sk span{background:color-mix(in srgb,var(--ink) 9%,transparent);border-radius:8px}
.sk__img{height:120px;border-radius:12px}
.sk__row{display:flex;align-items:center;gap:10px;background:none!important}
.sk__av{width:32px;height:32px;border-radius:50%!important;flex:none}
.sk__l{display:block;height:12px;width:100%}.sk__l--w{width:60%}.sk__l--s{width:40%}
.sk::after{content:"";position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--stage) 70%,transparent),transparent);animation:sk 1.4s ease-in-out infinite}
@keyframes sk{to{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){.sk::after{animation:none}}
skeleton-card.tsx
const css = `
.sk{position:relative;overflow:hidden;width:280px;padding:16px;border:1px solid rgba(127,127,127,.25);border-radius:16px;display:grid;gap:12px}
.sk>*,.sk span{background:rgba(127,127,127,.16);border-radius:8px}
.sk__img{height:120px;border-radius:12px}
.sk__row{display:flex;align-items:center;gap:10px;background:none!important}
.sk__av{width:32px;height:32px;border-radius:50%!important;flex:none}
.sk__l{display:block;height:12px;width:100%}.sk__l--w{width:60%}.sk__l--s{width:40%}
.sk::after{content:"";position:absolute;inset:0;transform:translateX(-100%);background:linear-gradient(90deg,transparent,rgba(255,255,255,.55),transparent);animation:sk 1.4s ease-in-out infinite}
@keyframes sk{to{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){.sk::after{animation:none}}`;
export function SkeletonCard({ width = 280 }: { width?: number }) {
return (
<>
<style>{css}</style>
<div className="sk" style={{ width }} aria-busy="true" aria-label="Loading card">
<div className="sk__img" />
<div className="sk__row"><span className="sk__av" /><span className="sk__l sk__l--w" /></div>
<span className="sk__l" />
<span className="sk__l sk__l--s" />
</div>
</>
);
}
export default SkeletonCard;
Install
npx shadcn@latest add https://ownlyplans.com/r/bhaskar/skeleton-card.json
Adds the React files to components/ownlyplans/ in a project set up with shadcn and Tailwind CSS. Or copy them from the Code tab.
About
Use it while content loads so the layout does not jump. The sweep is a single gradient on a pseudo-element.