CircleSkeleton
Round loading placeholder with a sweeping highlight, for an avatar or an icon that has not
arrived yet. Its size comes from radius, and the centre has to be moved to match.
Use this when / not when
- Use for a round thing that is loading: an
Avatar, a room logo, a circular icon. - Not for anything rectangular —
RectangleSkeletonis the one that takes a width and a height directly. - Not when the wait has no known shape:
Loader. - Not in a server-rendered tree. It cannot be given a stable id, so its SVG ids differ between server and client; see the behaviour notes.
Import
import { CircleSkeleton } from "@onlyoffice/apps-ui-kit/components/circle";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
No provider needed: the component draws itself in a fixed black at low opacity and reads nothing from the theme.
Stories
Default
A circle of radius 20 centred in a 50 by 50 box — the radius, the centre and the box size are set together, because the component's own defaults cut the circle off. Change any prop live in the Controls panel below.
Small Avatar
Small avatar-sized circle skeleton, suitable for compact user avatars.
Large Avatar
Large avatar-sized circle skeleton, suitable for profile images.
Custom Colors
A light grey circle for a surface where the default black at low opacity is too faint or the wrong tone, such as a dark one (backgroundColor, foregroundColor and their opacities).
No Animation
A still circle with no sweeping band, for a page that must not animate or a reader who asked for reduced motion (animate).
Slow Animation
The band takes 2.5 seconds per sweep instead of 2, for a calmer placeholder on a page that waits longer (speed).
Avatar Group
Multiple circle skeletons arranged in a row, simulating an avatar group placeholder.
Css Customization
The component reads no CSS custom property -- see the behaviour notes on this page; its colours come from props. All three circles here set the same four props, at three sizes.

Minimal example
The three numbers go together: a circle of radius r has to be centred at r, r, in an SVG of 2r — otherwise it is cut off by the edge.
import { CircleSkeleton } from "@onlyoffice/apps-ui-kit/components/circle";
export function AvatarPlaceholder() {
return (
<CircleSkeleton
x="16"
y="16"
radius="16"
width="32px"
height="32px"
title="Loading the avatar"
/>
);
}
Props
Recipes
Still, on a page that must not animate
import { CircleSkeleton } from "@onlyoffice/apps-ui-kit/components/circle";
export function StillAvatarPlaceholder() {
return (
<CircleSkeleton
x="20"
y="20"
radius="20"
width="40px"
height="40px"
animate={false}
/>
);
}
A list of people that has not loaded
import { CircleSkeleton } from "@onlyoffice/apps-ui-kit/components/circle";
import { RectangleSkeleton } from "@onlyoffice/apps-ui-kit/components/rectangle";
export function MemberListPlaceholder({ rows }: { rows: number }) {
return (
<div
style={{ display: "flex", flexDirection: "column", gap: 12 }}
aria-busy="true"
>
{Array.from({ length: rows }, (_, index) => (
<div
key={index}
style={{ display: "flex", alignItems: "center", gap: 12 }}
>
<CircleSkeleton
x="16"
y="16"
radius="16"
width="32px"
height="32px"
/>
<RectangleSkeleton width="160px" height="16px" />
</div>
))}
</div>
);
}
Behaviour the types don't state
- The defaults draw a clipped circle.
x="3",y="12",radius="12"put the centre three units from the left edge of a circle twelve units wide, so its left third falls outside the element and is cut off.<CircleSkeleton />on its own is not a circle; always pass the three values together. widthandheightsize the element,radiussizes the circle. They are separate, andheightdefaults to"100%", which collapses to nothing in a parent without a height. Give the element the diameter you want, as every example here does.- It has no accessible name by default. The SVG carries
role="img"and anaria-labelledbypointing at a<title>that is not rendered, becausetitledefaults to"". Pass atitleor mark the containeraria-busy. - The colours are fixed, not themed. Black at 10% and 15% opacity, whatever the theme, so on
a dark surface it nearly disappears.
backgroundColorandforegroundColorare the way in: they are written as the gradient stops'stop-colorattribute, and avar()there resolves against the page, so a pair declared under.lightand.darkand passed asbackgroundColor="var(--my-skeleton)"follows the theme as it switches. - It is not safe to server-render. The underlying library builds its gradient and clip-path
ids from a random value unless it is given a
uniqueKey, and this component neither accepts one nor supplies a stable one — so SSR and the client produce different markup.RectangleSkeletondoes supply one. data-testidis written after the spread, so it cannot be overridden; anything else you pass lands on the<svg>.
Accessibility
role="img"comes from the library. Without atitleit is an unnamed image and a screen reader announces nothing.- Mark the region
aria-busy="true"while the placeholders are showing; the component does not do it for you. animate={false}is the answer for reduced motion — nothing here checksprefers-reduced-motion.
Test ids
It cannot be overridden by a prop, so several skeletons on a page share it — query them with
getAllByTestId.
Related
RectangleSkeleton— the rectangular placeholder, and the one to pair this with in a row.Avatar— what this usually stands in for.Loader— a spinner, for work whose shape is unknown.