Skeletons

Placeholder screens shown while a page's data loads, and how to choose or replace them.

A skeleton is a grey placeholder in the shape of the page. The engine shows one while a route's loader fetches data, so the screen does not freeze after a click. You rarely render one yourself: you choose which one a route shows.

Example

app/routes/cart.tsx
import { createFileRoute } from '@tanstack/react-router';
import { CartSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';

export const Route = createFileRoute('/{-$locale}/cart')({
  pendingComponent: () => <CartSkeleton />,
});

Built-in skeletons

All come from @salla.sa/twilight-theme-engine/skeleton.

ComponentLooks like
PageSkeletonA generic page: breadcrumb, title and a product grid. Used by every route unless it names its own.
ProductDetailSkeletonA product page: gallery, details and the add-to-cart form.
CartSkeletonThe cart.
BlogSkeletonA blog list or article.
HomeSkeletonThe home page.
CustomerPageSkeletonA page inside the customer account.
CustomerLayoutSkeletonThe customer account frame.
BreadcrumbSkeletonThe breadcrumb row. A building block.
ProductCardSkeleton, ProductGridSkeletonOne product card, or a grid of them. Building blocks.

PageSkeleton takes optional children. When you pass children, it renders them instead of the default layout, so you can keep the same entry point and supply your own shape.

When a skeleton appears

The router decides, not the skeleton. Two timings apply, and the engine sets them for you:

OptionDefaultMeaning
defaultPendingMs100Wait this long (ms) before showing a skeleton. Fast loads show none.
defaultPendingMinMs200Once shown, keep it at least this long (ms) so it does not flash.

Change them, or the default skeleton, in the createRouter call inside getRouter() in app/router.tsx:

app/router.tsx
import { createRouter } from '@salla.sa/twilight-theme-engine/tanstack';
import { PageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';

// inside getRouter()
const router = createRouter(routeTree, {
  defaultPendingComponent: () => <PageSkeleton />,
  defaultPendingMs: 150,
  defaultPendingMinMs: 200,
});

Gotchas

  • A skeleton shows only if the loader takes longer than defaultPendingMs. A cached or quick page goes straight to its content.
  • A route's own pendingComponent wins over the default one.
  • Make a skeleton close to the real layout's height. A very different size makes the page jump when the content arrives.

Next

On this page