Skeleton
Grey shapes that show where the content will appear. They are always hidden from screen readers, so the wait has to be announced somewhere as well. If there is no shape to show, or the wait is short, Spinner is simpler.
How to use it
Section titled “How to use it”<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/skeleton/skeleton.css"><!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css --><div class="lt-skeleton" data-variant="circle" aria-hidden="true"></div><div class="lt-skeleton" data-variant="text" aria-hidden="true"></div><div class="lt-skeleton case-last-line" data-variant="text" aria-hidden="true"></div><div class="lt-skeleton case-image" aria-hidden="true"></div>/* Your own classes. These two rules are all it takes. */.case-last-line {inline-size: 70%;}
.case-image {--lt-skeleton-height: var(--lt-spacing-12);}import "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/skeleton.css"lt is used in two ways, and which one depends on whether you create the element or find it.
If you create the markup, spread the attributes in. That is the way in React, Astro and anything else that renders:
import { lt } from "@lo-tek/interfaces/react"
<div {...lt.skeleton({ variant: "circle" })} />The import is from /react here, since the block is JSX. From there the keys are className and htmlFor. In Astro and anything else without React, import @lo-tek/interfaces and you get class and for.
If you have the element in a variable, set them on it with setAttributes. That is the way without JSX, whether you just created the element or found it in a page the server sent:
import { lt } from "@lo-tek/interfaces"
const line = document.createElement("div")
lt.setAttributes(line, lt.skeleton({ variant: "text" }))data-variant | Used for |
|---|---|
| (omitted) | A block, for images and cards |
text | One line of text |
circle | A profile picture |
The block and the text lines fill the width they are given, while circle is as wide as it is high. case-last-line and case-image are your own classes, not LO-TEK’s, and they are in the “Plain HTML” tab above together with the markup.
Set the width of the last line to less than 100 per cent. A paragraph where every line is the same length does not look like text.
The wait has to be announced somewhere
Section titled “The wait has to be announced somewhere”<div id="content" aria-busy="true"> <p class="lt-sr-only" role="status">Fetching your applications</p> <div class="lt-skeleton" data-variant="text" aria-hidden="true"></div> <div class="lt-skeleton" data-variant="text" aria-hidden="true"></div></div>A row of empty boxes being read out is worse than silence. That is why lt.skeleton() always sets aria-hidden="true", and the message goes in a separate element with role="status".
When the content arrives, remove aria-busy and empty the status element.
Classes you can style
Section titled “Classes you can style”| Class | What it is |
|---|---|
.lt-skeleton | The placeholder. The shape comes from data-variant. |
| Variable | Default |
|---|---|
--lt-skeleton-height | var(--lt-spacing-4), and var(--lt-spacing-10) in circle, where it also gives the width. text does not read it: there the height comes from --lt-font-size-m |
--lt-skeleton-radius | var(--lt-spacing-1), and var(--lt-spacing-0-5) in text |
The shimmer is a gradient moving across the surface, not a pulsing transparency, so the contrast against the page surface is the same throughout. If the user has asked for reduced motion, the surface stays still.
See Customising for how the variables and the layer work.
Accessibility
Section titled “Accessibility”- The shapes should resemble what is coming. A placeholder of three lines where the result is twenty makes the page jumpy when the content lands.
- Wait a little before showing them, as with a loading indicator. A flash of grey boxes is more disruptive than a short pause.
- Do not leave them in place if something fails. The user is then waiting for something that never comes. Replace them with an Alert saying what went wrong.