bottega

effects

Skeleton Shimmer Card

A card-shaped skeleton / content-placeholder loader rendered with pure CSS. It mimics a typical content card layout: a circle avatar placeholder on the left, two short headline lines to its right, and two full-width body lines below — all filled with a muted token-tinted block color. A diagonal highlight stripe sweeps left-to-right continuously via a CSS translateX keyframe animation, creating the classic shimmer effect that signals loading state without a spinner. Uses role="status" and aria-label for screen-reader feedback. Width and sweep speed are prop-configurable via CSS custom properties; colors are entirely token-driven so the skeleton themes correctly in both light and dark contexts.

Ported from Nawsome (MIT)

Loading content…

Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/skeleton-shimmer-card

Usage

Usagetsx
<SkeletonShimmerCard />

Props

PropTypeDefaultDescription
labelstringLoading content…Accessible label for the loading region (default "Loading content…"). Rendered as aria-label on the role="status" wrapper.
widthstringControls the overall scale of the card. The component is 240 × 130px by default; pass a CSS length to override, e.g. "320px".
speedstringDuration of one shimmer sweep cycle (default "1.2s"). Accepts any CSS time value — "0.8s" for faster, "2s" for slower.
classNamestringAdditional class names merged onto the root element.

Source

skeleton-shimmer-card.tsxtsx
import type React from "react";
import styles from "./skeleton-shimmer-card.module.css";

export type SkeletonShimmerCardProps = {
  /**
   * Accessible label for the loading region (default "Loading content…").
   * Rendered as aria-label on the role="status" wrapper.
   */
  label?: string;
  /**
   * Controls the overall scale of the card. The component is 240 × 130px
   * by default; pass a CSS length to override, e.g. "320px".
   */
  width?: string;
  /**
   * Duration of one shimmer sweep cycle (default "1.2s").
   * Accepts any CSS time value — "0.8s" for faster, "2s" for slower.
   */
  speed?: string;
  /** Additional class names merged onto the root element. */
  className?: string;
};

/**
 * A card-shaped skeleton / content-placeholder loader.
 * Renders a circle avatar placeholder, two headline lines, and two body lines,
 * with a horizontal shimmer sweep travelling left-to-right.
 */
export function SkeletonShimmerCard({
  label = "Loading content…",
  width,
  speed,
  className = "",
}: SkeletonShimmerCardProps) {
  const rootStyle: React.CSSProperties & Record<string, string> = {};
  if (width) rootStyle["--skeleton-width"] = width;
  if (speed) rootStyle["--skeleton-speed"] = speed;

  return (
    <div
      role="status"
      aria-label={label}
      className={[styles.card, className].filter(Boolean).join(" ")}
      style={rootStyle}
    >
      {/* decorative shimmer sweep — hidden from AT */}
      <span aria-hidden="true" className={styles.shimmer} />

      <div aria-hidden="true" className={styles.wrapper}>
        <div className={styles.circle} />
        <div className={styles.line1} />
        <div className={styles.line2} />
        <div className={styles.line3} />
        <div className={styles.line4} />
      </div>

      {/* visually-hidden "Loading…" text for screen readers that don't read aria-label on non-interactive roles */}
      <span className={styles.srOnly}>{label}</span>
    </div>
  );
}

Dependencies

  • @bottega/tokens