A loading placeholder that reads the font actually in effect (size, line-height, x-height, ascent, descender depth) and lays out gray blobs the way a real paragraph would wrap — including a shorter last line.
Click to swap between the real paragraph and its skeleton at the exact same width/font. The box height should not jump.
Web components let you ship a custom element without a framework: register a tag, attach a shadow root, and the browser handles the rest — including style encapsulation and lifecycle callbacks like connectedCallback and attributeChangedCallback.
text attribute
Feed the skeleton the same real string via the
text attribute: word lengths, per-character
ascender/x/descender shape, and glyph widths all come from the real
letters, so it wraps to the same line count as the real
paragraph below, at the same width.
checking…
Without a text attribute, blob shapes are invented from the
variance
knob (0–1) instead of real letters. Skimmed real prose reads mostly flat
to the eye, so the default (0.2) stays close to that; 1 matches the
actual letter-shape frequency of English — busier than most people
expect.
Same component, no props changed except the surrounding font — ascent, x-height and descender depth are re-measured from the computed style each time.
Real text flowing right into a
Drag the bottom-right corner of the box below — line breaks and the short last line recompute for the new width, same as real text.
animated="pulse" (default) shimmers every blob in sync.
animated="sweep" runs one light band across the whole block
via a single shared requestAnimationFrame loop (not
per-instance). animated="typing" reveals blobs
left-to-right/line-by-line, holds, erases in the same order, and repeats
— driven entirely by staggered animation-delay, no JS
timer.
Separate blobs read as a dashed line. connect="word" fills
the gaps within each word with an x-height bar (word rhythm
stays visible). connect="line" fills the gaps across the
whole line too, including the inter-word spaces — no word boundaries
left, closer to a redaction bar. Ascenders/descenders still poke out
either way.
A realistic title + subtitle + 2-line body + footer actions card. Real
content on the left, an all-<text-skeleton>
placeholder on the right — same markup, same CSS, just the text nodes
swapped for the component. The controls below restyle every skeleton in
the card live.
Zurich, Switzerland · Full-time · Remote-friendly
We're looking for an experienced engineer to help us build the next generation of our platform.
A different job from everything above: that's all a placeholder for
content you don't have yet. This wraps markup that's already
there and swaps its text for matching
<text-skeleton text="…"> placeholders on demand —
redacted screenshots, screen recordings, "blur sensitive fields" UI.
It never invents anything; every placeholder is driven by the real
text it's standing in for, so toggling never shifts layout. The icon
in "Apply now" is marked data-censor-skip and stays put
either way. This card is intentionally small — see
censor-demo.html for a full page
(nav, table, accordion, form) stress-testing the same wrapper.
Zurich, Switzerland · Full-time · Remote-friendly
We're looking for an experienced engineer to help us build the next generation of our platform.