Skip to content

Skeleton Loader

A shimmering placeholder shown while content is loading.

A shimmering placeholder shown while content is loading. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.

Root class newt-skeleton
import { Skeleton } from "@/registry/bases/newt/ui/skeleton"

export default function SkeletonDemo() {
  return (
    <div className="flex w-full max-w-sm gap-3">
      <Skeleton variant="avatar" />
      <div className="flex flex-1 flex-col gap-1.5">
        <Skeleton variant="title" />
        <Skeleton variant="text" className="w-[80%]" />
      </div>
    </div>
  )
}
<script setup lang="ts">
import { Skeleton } from "~~/registry/bases/newt/ui/skeleton"
</script>

<template>
  <div class="flex w-full max-w-sm gap-3">
    <Skeleton variant="avatar" />
    <div class="flex flex-1 flex-col gap-1.5">
      <Skeleton variant="title" />
      <Skeleton variant="text" class="w-[80%]" />
    </div>
  </div>
</template>

Installation

pnpm dlx newtui@latest add skeleton
npx newtui@latest add skeleton
yarn dlx newtui@latest add skeleton
bunx newtui@latest add skeleton

Install the dependencies

pnpm dlx --yes class-variance-authority
npx --yes class-variance-authority
yarn dlx --yes class-variance-authority
bunx --yes class-variance-authority

Copy the source into your project

import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"

import { cn } from "@/lib/utils"

const skeletonVariants = cva(
  "animate-[newt-skeleton-shimmer_1.6s_ease-in-out_infinite] rounded-sm bg-[linear-gradient(90deg,var(--newt-bg-elevated)_25%,var(--newt-bg-hover)_50%,var(--newt-bg-elevated)_75%)] bg-[length:200%_100%] motion-reduce:animate-none motion-reduce:opacity-60",
  {
    variants: {
      variant: {
        default: "",
        text: "h-[14px]",
        avatar: "h-10 w-10 rounded-full",
        title: "h-5 w-[40%]",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

export interface SkeletonProps
  extends React.ComponentProps<"div">, VariantProps<typeof skeletonVariants> {}

const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
  ({ className, variant, ...props }, ref) => (
    <>
      <style>{`@keyframes newt-skeleton-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}`}</style>
      <div
        ref={ref}
        aria-hidden="true"
        data-variant={variant ?? "default"}
        className={cn(skeletonVariants({ variant }), className)}
        {...props}
      />
    </>
  )
)
Skeleton.displayName = "Skeleton"

export { Skeleton, skeletonVariants }
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

import { skeletonVariants, type SkeletonVariants } from "."

const props = defineProps<{
  class?: HTMLAttributes["class"]
  variant?: SkeletonVariants["variant"]
}>()
</script>

<template>
  <div
    aria-hidden="true"
    :data-variant="props.variant ?? 'default'"
    :class="cn(skeletonVariants({ variant: props.variant }), props.class)"
  />
</template>

<style>
@keyframes newt-skeleton-shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}
</style>
import { cva, type VariantProps } from "class-variance-authority"

export { default as Skeleton } from "./Skeleton.vue"

export const skeletonVariants = cva(
  "animate-[newt-skeleton-shimmer_1.6s_ease-in-out_infinite] rounded-sm bg-[linear-gradient(90deg,var(--newt-bg-elevated)_25%,var(--newt-bg-hover)_50%,var(--newt-bg-elevated)_75%)] bg-[length:200%_100%] motion-reduce:animate-none motion-reduce:opacity-60",
  {
    variants: {
      variant: {
        default: "",
        text: "h-[14px]",
        avatar: "h-10 w-10 rounded-full",
        title: "h-5 w-[40%]",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

export type SkeletonVariants = VariantProps<typeof skeletonVariants>

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

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

export function Example() {
  return <Skeleton />
}
<script setup lang="ts">
import { Skeleton } from "@/components/ui/skeleton"
</script>

<template>
  <Skeleton />
</template>

API reference

Skeleton

PropType
children / default slot?React.ReactNode / Vue slot

Content rendered inside the component.

TypeReact.ReactNode / Vue slot
className / class?string

Additional classes merged with the component defaults.

Typestring

The registry exports Skeleton. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.

Accessibility

Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.

Tokens

Skeleton Loader reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-skeleton.