Skeleton
هيكل التحميلaria-busy="true"
also calledskeleton screen، skeleton loader، content placeholder، shimmer، ghost loader، سكيليتون، الهيكل التحميلي، شاشة الهيكل، العناصر النائبة، هيكل المحتوى، شيمر
A skeleton is a stripped-down grey stand-in for the interface, drawn in the same places and at the same sizes as the content still being fetched. Its job is to tell you the shape of the page before the data arrives, so the wait feels shorter and nothing jumps when it lands. The difference from a spinner is that a spinner says "wait" and says nothing about layout, while a skeleton says "this is what is coming, and this is where". A progress bar reports a known fraction of measurable work, and a skeleton has no idea how much is left. An empty state is a final answer meaning "there is nothing here", while a skeleton is a temporary promise that data is on the way. A skeleton that never resolves is worse than a plain error. The governing rule: if the skeleton does not match the shape and size of the real content, the layout jumps when the data lands, and you have created the problem you were trying to hide.
If you called it…
Live specimen
Interact with the demo. Every part is real and numbered.
Anatomy: every part, named
Hover a row to locate it in the demo above.
Build prompt
How it behaves right-to-left
This section is ours alone.
Path of the sweep
mirrorsThe sweep travels with the reading direction: left-to-right in English, right-to-left in Arabic. transform is a physical property and never flips with dir, so a hard-coded translateX(-100%) runs the shimmer backwards on an Arabic page. Drive it from a variable instead: --dir: 1 on the root, --dir: -1 under [dir="rtl"], then animate translateX(calc(100% * var(--dir))). For a plain linear sweep, animation-direction: reverse under [dir="rtl"] does the same job in one line.
Tilt of the gradient
mirrorsA linear-gradient angle is physical too: CSS still has no logical keyword such as "to inline-end" for gradients. A 110deg shimmer leans the wrong way once the page flips, so compute it from the same direction variable: linear-gradient(calc(90deg + 20deg * var(--dir)), …). The same applies to background-position if you animate the background instead of the element.
The geometry of the blocks
never mirrorsDo not fix the sweep direction by flipping the whole skeleton with transform: scaleX(-1). The blocks are not directional: mirroring them inverts asymmetric border-radius corners, reverses any placeholder icon inside, and flips the direction of any clip-path or mask-image on them. Keep the flip inside the motion (the --dir value in the keyframes) and leave the boxes alone. If you need asymmetric corners, write them as border-start-start-radius and border-start-end-radius so they mirror on their own.
The short last line
mirrorsThe last bar in a paragraph starts at the logical start and falls short at the logical end: in Arabic it starts on the right and stops before the left edge. A block box with width: 55% and zero margins does this by itself, but margin-right: auto pins it to the wrong side, so use margin-inline-end: auto. A fade added with mask-image: linear-gradient(to right, …) is physical and needs an explicit flip under RTL.
Skeleton padding must equal component padding
mirrorsThe skeleton and the real component must share the exact same padding and gap values. Write padding-inline-start on the real card and padding-left on its skeleton, and the two agree in English but diverge in Arabic, so the layout jumps on data arrival in Arabic only. That is the worst kind of bug, because it never surfaces in your own testing.
In code
Each row is one framework's word for the same thing. Take the row your project speaks.
See also
Updated · 2026-08-19