DEV Community

Cover image for Design Skeletons by Character Count — Tailwind CSS v4
Yuki Teraoka
Yuki Teraoka

Posted on

Design Skeletons by Character Count — Tailwind CSS v4

Still stacking gray boxes with h-* and w-* for skeletons? That approach has problems:

  • Layout shifts when switching from skeleton to actual text
  • Duplicate markup for skeleton-specific elements that don't match your real content structure

This package lets you specify skeletons by character count instead. It only renders when the element is empty, so your normal markup stays intact—no layout shifts, no extra skeleton-only markup.

Try the demo →

Repo: https://github.com/t4y3/tailwindcss-skeleton-screen

overview

Concept

Keep markup as close to normal as possible.

  • Render skeletons only when the element is :empty.
  • Use ss-text-[n] to generate a text skeleton for n characters.
  • Height follows font-size/line-height, so it aligns with typography.
{/* Skeleton is shown only while empty */}
<p className="text-base text-gray-600 ss-text-[71]">{text}</p>
Enter fullscreen mode Exit fullscreen mode

The difference from typical width/height rectangles is visually clear:

diff

Installation (Tailwind CSS v4)

npm install -D tailwindcss-skeleton-screen
Enter fullscreen mode Exit fullscreen mode

Load it in your Tailwind v4 entry CSS (no tailwind.config.js needed):

/* tailwind.css (your entry processed by Tailwind v4) */
@import "tailwindcss-skeleton-screen";
Enter fullscreen mode Exit fullscreen mode

Basic Usage

  • Just specify the character count; height follows your typography.
<!-- Text skeleton appears only while the element is empty -->
<p class="text-base text-gray-600 ss-text-[71]"></p>
Enter fullscreen mode Exit fullscreen mode

For multiple lines, specify per-line counts with slashes:

<p class="ss-text-[24/24/12]"></p>
Enter fullscreen mode Exit fullscreen mode

Single-line truncation works too:

<p class="truncate ss-text-[40]"></p>
Enter fullscreen mode Exit fullscreen mode

Utilities

  • ss-object: Block skeleton with background color
  • ss-text-[n]: Text skeleton for n characters
  • ss-text-[a/b]: Per-line counts by slash (multiline)

Notes:

  • Skeletons render only when the element is :empty.
  • Combine freely with text utilities; it follows your typography.

Customize (v4 @theme)

Colors and radii are exposed as CSS custom properties. Override via Tailwind v4 @theme.

@theme {
  --skeleton-color: #e5e7eb; /* default */
  --skeleton-radius: .375rem; /* default (~6px) */
}
Enter fullscreen mode Exit fullscreen mode

Global overrides:

@theme {
  --skeleton-color: #f3f4f6;
  --skeleton-radius: .5rem;
}
Enter fullscreen mode Exit fullscreen mode

Scoped overrides (apply to descendants):

.card {
  --skeleton-color: #e2e8f0;
  --skeleton-radius: .25rem;
}
Enter fullscreen mode Exit fullscreen mode

React Example (render only when empty)

export function UserBio({ bio }) {
  return (
    <p className="text-sm leading-6 text-gray-600 ss-text-[72]">
      {bio}
    </p>
  );
}
Enter fullscreen mode Exit fullscreen mode

The skeleton shows while bio is empty and naturally switches to text once populated.

How It Works

  • The plugin parses ss-text-[...] and builds a string of full‑width spaces (U+3000).
  • It injects that as --tw-content into ::before only when the element is :empty.
  • Background and radius are controlled by CSS variables. It matches your line-height and wrapping.
[class*="ss-text-"]:empty::before {
  content: var(--tw-content);
  background-color: var(--skeleton-color);
  white-space: break-spaces;
  word-break: break-all;
  border-radius: var(--skeleton-radius);
}
Enter fullscreen mode Exit fullscreen mode

Migration from v3

  • No more plugin registration in tailwind.config.js.
  • Use v4 CSS directives (@import / @plugin).
  • Theme options moved to @theme CSS custom properties.

Best Practices

  • For variable-length content, estimate slightly larger character counts to reduce visual shift.
  • For layouts with variable line counts, use ss-text-[a/b/c] for the maximum expected lines.
  • Combine with truncate to match “1 line + ellipsis” from the loading state.

Wrap-up

Designing skeletons by “character count” aligns them with your typography and makes loading states feel natural. Setup is just @import and a utility class. Try it on your existing components.

Top comments (0)