Today, after waiting for a Pitchfork review to load, I built something small I’d like to share: my own take on the Pitchfork aggregate review loading experience.
Pitchfork uses a Skeleton-style UI while returning an album’s aggregate review, such as “6.9” for Mastodon’s Marrow Deep. Skeleton UIs show you something, like blurred text or a flashing bar, to build anticipation and fill out the page in a load state.
They are a fantastic UX touch for the right kind of app and data. In the case of the Pitchfork app, the aggregate review is a pivotal piece of information about an album, and is possibly being calculated on demand, so this choice makes sense.
My version
Here’s the version that I built. I added skeleton loading on a review summary, which was not part of the original design.

Why did I build this? I had curiosity and time. I’ve also felt that React Skeleton UI libraries always seem to do more than I want them to. I was wondering if I could build something smaller for a constrained use case, and I did.
The module code
I built this in two parts: a Node module handling the skeleton logic, and an application to consume it. I’ll share a few small learnings at the end.
Here’s the module:
// react-skeleton-loader/index.tsx
export interface SkeletonTextProps {
blur?: number;
className?: string;
placeholder: string;
value?: string;
}
export function SkeletonText({
blur = 4,
className,
placeholder,
value,
}: SkeletonTextProps) {
const loading = value === undefined;
const blurRadius = Math.min(12, Math.max(0, blur));
return (
<div className={className}>
<span
style={{
filter: loading ? `blur(${blurRadius}px)` : 'none',
}}
>
{loading ? placeholder : value}
</span>
</div>
);
}
A couple of noteworthy points:
- A
blurprop that usesfilter: blur(radius)to customize how blurred the placeholder is. The album’s rating will be more blurred than copy below it . - Inline styling and a placeholder value to create the skeleton experience.
- A
classNameso users can style the component; this library is about behavior, not style.
The consumer code
And here’s the consumer, edited for clarity.
// App.tsx
import {SkeletonText} from 'react-skeleton-loader';
const App = () => (
<>
<SkeletonText blur={9} placeholder="0.0" value={rating} />
<SkeletonText blur={3} placeholder="A world appears" value={copy} />
</>
);
We calculate rating and copy in a side-effect with a few-seconds delay, to
simulate the loading experience.
What I learned
I’m going to share a few TILs here, now and as I add them.
- tsc watch script – The command I used to rebuild my module on write
- Link to Local Package With npm link – how I iterated on my local module in development
blur() CSS function– MDN