HextaUI

Skeleton

Göstermeden önce 150ms bekleyen, sardıkları içeriğin tam boyutunu alan ve hiçbir şeyi kaydırmadan içeriği yumuşakça görünür kılan yer tutucular.

import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonDemo() {
  return (
    <div className="flex w-full max-w-sm items-center gap-4">
      <Skeleton className="size-12 rounded-full" />
      <div className="flex flex-1 flex-col gap-2">
        <Skeleton className="h-4 w-3/5" />
        <Skeleton className="h-4 w-4/5" />
      </div>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/skeleton.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

Shimmer ve nabız, temanızdaki iki animasyonu kullanır. Bunları global CSS dosyanıza bir kez ekleyin.

@import "tw-animate-css";

@theme {
  --animate-shimmer: shimmer 1.6s ease-in-out infinite;
  --animate-skeleton-pulse: skeleton-pulse 2s ease-in-out infinite;

  @keyframes shimmer {
    from {
      translate: calc(-100% * var(--skeleton-dir, 1)) 0;
    }
    to {
      translate: calc(100% * var(--skeleton-dir, 1)) 0;
    }
  }

  @keyframes skeleton-pulse {
    0%,
    100% {
      opacity: 0;
    }
    50% {
      opacity: 0.5;
    }
  }
}

Skeleton'u boyutlandırılmış bir yer tutucu olarak tek başına kullanın veya yer tutucu gerçek içeriğin tam boyutunu alsın diye loading verip gerçek içeriği sarın.

import { Skeleton, SkeletonText } from "@/components/ui/skeleton"
<Skeleton className="h-4 w-32" />

<Skeleton loading={isLoading}>
  <h3>{user.name}</h3>
</Skeleton>

Şekiller

Tek başına bir skeleton boş bir bloktur. Yerine geçtiği şeye uysun diye sınıflarla ona boyut ve yarıçap verin.

import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonShapes() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <div className="flex items-center gap-4">
        <Skeleton className="size-12 rounded-full" />
        <div className="flex flex-1 flex-col gap-2">
          <Skeleton className="h-4 w-2/5" />
          <Skeleton className="h-4 w-3/5" />
        </div>
        <Skeleton className="h-8 w-20" />
      </div>
      <Skeleton className="aspect-video w-full rounded-xl" />
    </div>
  )
}

Animasyonlar

animation süpüren bir shimmer, yumuşak bir pulse veya none seçer.

import { Skeleton, type SkeletonAnimation } from "@/components/ui/skeleton"

const animations: SkeletonAnimation[] = ["shimmer", "pulse", "none"]

export function SkeletonAnimations() {
  return (
    <div className="grid w-full max-w-md grid-cols-3 gap-3">
      {animations.map((animation) => (
        <div key={animation} className="flex flex-col gap-2">
          <Skeleton animation={animation} className="h-16 w-full" />
          <span className="text-xs text-muted-foreground">{animation}</span>
        </div>
      ))}
    </div>
  )
}

Metin

<SkeletonText /> satır başına bir çubuk çizer ve üst öğenin yazı tipi boyutunu ve satır yüksekliğini izler; böylece yerine geçtiği metinle aynı alanı doldurur. Son satır daha kısadır.

import { SkeletonText } from "@/components/ui/skeleton"

export function SkeletonTextDemo() {
  return (
    <div className="grid w-full max-w-md grid-cols-3 gap-6">
      <div className="text-sm">
        <SkeletonText lines={3} />
      </div>
      <div className="text-lg">
        <SkeletonText lines={3} />
      </div>
      <div className="text-sm leading-8">
        <SkeletonText lines={3} />
      </div>
    </div>
  )
}

Gerçek içeriği sarın

loading ile skeleton gerçek içeriği altta görünmez olarak işler; böylece tam boyutu alır ve veri geldiğinde hiçbir şey kaymaz. loading false olunca içerik solarak belirir.

"use client"

import * as React from "react"
import { IconUserPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonWrapContent() {
  const [loading, setLoading] = React.useState(true)

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <div className="flex items-start gap-4 rounded-xl border p-4">
        <Skeleton loading={loading} className="rounded-full">
          <img
            src="/preview/landscape.svg"
            alt=""
            className="size-12 rounded-full object-cover"
          />
        </Skeleton>
        <div className="flex min-w-0 flex-1 flex-col gap-1">
          <Skeleton loading={loading}>
            <h3 className="font-semibold">Olivia Martin</h3>
          </Skeleton>
          <Skeleton loading={loading}>
            <p className="text-sm text-muted-foreground">
              Design engineer at Acme. Writes about motion and small details.
            </p>
          </Skeleton>
        </div>
        <Skeleton loading={loading}>
          <Button size="sm" variant="outline">
            <IconUserPlus data-icon="inline-start" />
            Follow
          </Button>
        </Skeleton>
      </div>
      <div className="flex gap-2">
        <Button size="sm" onClick={() => setLoading(false)}>
          Load
        </Button>
        <Button size="sm" variant="ghost" onClick={() => setLoading(true)}>
          Reset
        </Button>
      </div>
    </div>
  )
}

Satır içi

Bir skeleton'u cümle içine yerleştirmek için render={<span />} verin. Metnin taban çizgisine oturur.

import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonInline() {
  return (
    <p className="text-sm">
      Your balance is{" "}
      <Skeleton loading render={<span />}>
        <strong>$12,480.00</strong>
      </Skeleton>{" "}
      as of today.
    </p>
  )
}

Hızlı yükleme

Bir skeleton ilk 150ms boyunca görünmez kalır, sonra solarak belirir. Daha erken gelen veri hiç yer tutucu çakıştırmaz.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonFastLoad() {
  const [loading, setLoading] = React.useState(true)
  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)

  React.useEffect(() => () => clearTimeout(timer.current), [])

  function run() {
    clearTimeout(timer.current)
    setLoading(true)
    timer.current = setTimeout(() => setLoading(false), 80)
  }

  return (
    <div className="flex flex-col items-center gap-3">
      <Skeleton loading={loading}>
        <p className="text-sm">
          Loaded in 80ms, so the skeleton never became visible.
        </p>
      </Skeleton>
      <Button size="sm" onClick={run}>
        Run fast load
      </Button>
    </div>
  )
}

Çok satır

Birlikte mount olan skeleton'lar animasyonlarına birlikte başlar; böylece uzun bir liste tek bir yükleme alanı gibi okunur.

import { Skeleton } from "@/components/ui/skeleton"

export function SkeletonManyRows() {
  return (
    <div className="flex max-h-64 w-full max-w-sm flex-col gap-3 overflow-y-auto">
      {Array.from({ length: 50 }, (_, index) => (
        <div key={index} className="flex items-center gap-3">
          <Skeleton className="size-8 rounded-full" />
          <Skeleton className="h-3 w-1/2" />
        </div>
      ))}
    </div>
  )
}

Sağdan sola

Sağdan sola düzende shimmer sağdan sola süpürür.

import { Skeleton, SkeletonText } from "@/components/ui/skeleton"

export function SkeletonRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-sm items-center gap-4 text-sm">
      <Skeleton className="size-12 rounded-full" />
      <div className="flex-1">
        <SkeletonText lines={2} />
      </div>
    </div>
  )
}
  • Tek başına bir skeleton dekoratiftir ve yardımcı teknolojilerden gizlidir; <SkeletonText /> da öyle.
  • İçeriği saran bir skeleton yüklenirken aria-busy ayarlar. Alttaki içerik yardımcı teknolojilerden gizlenir ve yüklenene kadar odaklanılamaz.
  • Skeleton'lar hiçbir şey duyurmaz. İnsanların neyin yüklendiğini bilmesi gerekiyorsa görünür bir etiket veya durum mesajı ekleyin.
  • Azaltılmış hareket açıkken skeleton'lar shimmer, nabız veya solma olmadan hemen görünür.

İşlediği öğenin tüm niteliklerini kabul eder.

PropTürVarsayılan
animation
"shimmer" | "pulse" | "none""shimmer"
loadingAyarlandığında skeleton alt öğelerini sarar: true yer tutucuyu, false içeriği gösterir. Bağımsız bir yer tutucu için vermeyin.
boolean–
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="skeleton"CSS'te skeleton'ları hedefler.
data-animationKullanılan animasyon. Sarılan içerik yüklendikten sonra kaldırılır.
data-loadingİskelet içeriği sardığında "true" veya "false".
data-slot="skeleton-content"Gerçek içeriği sarar. Yüklenirken görünmez ve etkisizdir.
--skeleton-dir1 ya da sağdan sola düzenlerde -1. Shimmer'ın yönünü belirler.

Tüm <div> niteliklerini kabul eder.

PropTürVarsayılan
linesAşağı yuvarlanır ve 1 ile 50 arasında tutulur.
number3
animation
"shimmer" | "pulse" | "none""shimmer"
ÖznitelikAçıklama
data-slot="skeleton-text"Satırlar için kapsayıcı.

Kullanıldığı bloklar

Skeleton üzerine kurulan bloklar.