Loading

Show that something is loading, in the place and shape of what is coming, without implying how long it takes.

BetaSince 1.0.0
On this page

Import

import {
  Avatar,
  Button,
  Card,
  Column,
  Grid,
  List,
  NavigationDrawer,
  NavigationRail,
  Page,
  PageHeader,
  placeholders,
  Progress,
  Row,
  Scaffold,
  SegmentedControl,
  Spinner,
  Text,
  TextField,
  TopBar,
} from '@envsemble/envui';

Usage

Render the page's own components with skeleton while the data loads, over placeholders<Item>(count).

  • items ?? placeholders<Item>(3) gives them stand-ins to map over.
  • A text prop given as undefined renders as a bar of its text style's length.
  • Wait 200 ms before showing the skeleton, through a timer of the application's own, and keep its space meanwhile, as the SkeletonList example does.
CardsLoading.tsx
import { useEffect, useState } from 'react';
import { Card, Grid, placeholders } from '@envsemble/envui';

interface Project {
  id: string;
  name: string;
  updated: string;
  summary: string;
}

const projects: Project[] = [
  { id: 'brief', name: 'Launch brief', updated: 'Today', summary: 'Goals and dates.' },
  { id: 'budget', name: 'Budget', updated: 'Monday', summary: 'Spend by quarter.' },
  { id: 'survey', name: 'Survey', updated: 'May 4', summary: 'Answers so far.' },
];

export default function CardsLoading() {
  const [items, setItems] = useState<Project[]>();
  useEffect(() => {
    // The projects load from a server as the page opens; a timer stands in for it here.
    const timer = window.setTimeout(() => setItems(projects), 1600);
    return () => window.clearTimeout(timer);
  }, []);
  return (
    // The same cards, over placeholder items while the projects load, so they land in place.
    <Grid columns={{ min: '12rem' }} gap={4} style={{ inlineSize: '100%' }}>
      {(items ?? placeholders<Project>(3)).map((project) => (
        <Card
          key={project.id}
          skeleton={!items}
          title={project.name}
          meta={project.updated}
          description={project.summary}
        />
      ))}
    </Grid>
  );
}

A skeleton List, as the SkeletonList example shows it:

<List.Root aria-label="Design projects" skeleton={!items}>
  {(items ?? placeholders<Project>(3)).map((project) => (
    <List.Item key={project.id} label={project.name} description={project.owner} />
  ))}
</List.Root>

Indicators

What is loadingCode
Content of a known shapeIts components with skeleton, over placeholders()
Content of no known shape<Spinner label="Searching documents" showLabel />
An update to what is shown<Spinner size="sm" label="…" /> beside it, aria-busy on the region
The result of an actionpending on the Button that started it
Work whose end can be measured<Progress label={file.name} value={percent} />
A whole pageloading on the TopBar, named by loadingLabel

A TopBar with loading shows a whole page on its way; the page on screen stays current until the next arrives, as the PageProgress example does.

<TopBar title="Northwind" loading={next !== null} loadingLabel="Loading Projects" />

Media

Give media an aspect ratio, with width and height or the CSS aspect-ratio, so it holds its place. Dev mode's envui.layout.skeletonShift() reports what moves as content replaces its skeleton.

Composition

A skeleton's root takes aria-busy and its content is inert, in env-skeleton-content, which has no box of its own. Bars have small rounded corners, over missing text and text that is there alike, and shapes keep their component's corners, such as a chip's, an avatar's round one or a card's. A skeleton List holds no wrapper, and beside it a visually hidden status says what is loading.

Accessibility

You provide

  • A label that names what is loading, on every indicator.
  • A status message where the wait is long, since the skeleton's content isn't read.
  • The delay before a skeleton or spinner appears.

envUI provides

  • One busy region per skeleton, aria-busy on its root, whose content is inert: Tab passes over it and assistive technology skips it until it loads.
  • A visually hidden status beside a skeleton list, which holds no wrapper, that says what is loading.
  • A skeleton popup, sheet or supporting pane that keeps focus, so Esc still closes it.
  • A Spinner with showLabel, a busy status with its label; a pending Button that keeps its size and ignores presses; Progress with its value.

Changelog

1.0.0

Skeletons are the components themselves, with skeleton and placeholders(), in place of hand-built Skeleton placeholders. The page is on the figure template.

1.0.0-prerelease.6

Only the bars and shapes pulse: the skeleton's own surfaces, such as cards and rows, keep a faint tint that barely moves, so they keep their edges against the page and the layout stands still.

A skeleton's breath lasts seven and a half times --env-motion-duration-slow, as long as before, with --env-motion-ease, so durations of 0s stop it.