Loaders✨ Premium

Text Loading

A ::after pseudo-element animated by @keyframes cycles through four content states — empty, one dot, two dots, three dots — in a 1.5 s loop; one single div, zero JavaScript.

CSSText

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →

3 usage examples

Submit button — the label signals its own state — Text Loading example 1

① Submit button — the label signals its own state

WhenContact or payment form: on click, the button text is replaced with a span.loader-text containing "Sending".
WhyUnlike a spinner added beside the text, this loader consumes no extra space inside the button. The slight width variation from the dots is absorbed by the container.
Settingsfont-size: 0.875rem (common button size); color: inherit to follow the parent label color; font-weight: 600 kept.
Order tracking — the active step says what you are waiting for — Text Loading example 2

② Order tracking — the active step says what you are waiting for

WhenDelivery tracking page: on the step timeline, the active line carries "Preparing your order" followed by the animated dots, between the completed steps above and the upcoming ones below.
WhyA spinner or an indeterminate bar would only say "working". Here the loader is a sentence, and the sentence names the step: the customer knows what they are waiting for. The dots say the timeline is moving, without inventing a percentage the seller does not have.
Settingsfont-size: 1.2rem, font-weight: 600 and color: #6366f1 from the code kept as is: the active step is the only indigo line and the largest in the list; label "Preparing your order".
Editor toolbar — discreet save indicator — Text Loading example 3

③ Editor toolbar — discreet save indicator

WhenText or document editor with autosave: a small indicator in the toolbar signals "Saving..." during the API call.
WhyA text-based loader stays inline with the other toolbar labels without imposing a spinner that draws too much attention. Size and color adjust in two CSS properties.
Settingsfont-size: 0.75rem; color: #71717a (neutral grey); label text "Saving".

How it works

The effect lives in a single <div class="loader-text">: the static label (here "Loading") is written directly in the HTML, and ::after carries the animation. The CSS sets font-size: 1.2rem, font-weight: 600 and color: #6366f1 (indigo) on the div. The pseudo-element starts with content: "" and receives animation: 1.5s ease 0s infinite normal none running loadingDots — duration 1.5 s, ease timing, zero delay, infinite loop, no fill mode. Measured in Chromium: 74.7 × 22 px for the text "Loading" at 19.2 px/600. No JavaScript.

@keyframes loadingDots defines four stops: 0% → "", 25% → ".", 50% → "..", 75% → "...". The content property is discrete: it does not interpolate, it switches. With the ease function, the switch happens when the Bézier curve reaches 50% of progress over each 375 ms interval — which does not coincide exactly with the half-interval mark. There is no 100% keyframe: "..." persists until the implicit midpoint of the 75%–100% interval, then the cycle restarts at "". Measured in Chromium: correct sequence, no console errors.

For four states of strictly equal duration (375 ms each), replace ease with steps(1, end) in the animation declaration, or add an explicit 100% { content: "" } keyframe. The effect is pure CSS: it integrates unchanged in an iframe, a Shadow DOM component or a button, without absolute positioning, without z-index and without any external dependency.

Accessibility

  • prefers-reduced-motion not handled: no @media (prefers-reduced-motion: reduce) rule suspends the animation. Measured in Playwright with reduce emulation: animationPlayState stays running. Fix: @media (prefers-reduced-motion: reduce) { .loader-text::after { animation: none; content: "…"; } }.
  • role="status" absent: a loading indicator must notify screen readers when it appears or disappears. Add role="status" (or aria-live="polite") to the div so that VoiceOver and NVDA announce the element on DOM insertion.
  • The animated dots (::after) are not read by screen readers: pseudo-elements are absent from the accessibility tree. The static text ("Loading") is announced correctly — the dots are decorative, which is acceptable as long as the text describes the state. If the div is left empty (dots only), add aria-label="Loading".
  • The effect is pure CSS and mouse-independent: it works identically on mobile, tablet and in any static container. No position: fixed, no z-index, no overflow — the div follows normal document flow.

Browser compatibility

Pure CSS: requires animation, @keyframes and content on pseudo-elements. No site CSS variables, no dependencies, no JavaScript.

Chrome 43+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without CSS animation support (IE 9 and earlier), the div shows the static text without dots — content remains readable, loading state communicated by the text alone. Without pseudo-element support, same result.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="loader-text">Loading</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Static label (HTML — div content) "Loading" Any string, including empty to show only the dots. The CSS does not change.
Color (CSS — color: #6366f1) #6366f1 (indigo) Color of both text and dots. The pseudo-element inherits color.
Size (CSS — font-size: 1.2rem) 1.2rem (19.2 px on a 16 px base) Size of the entire block — text and dots. Measured render: 74.7 × 22 px for "Loading" at 19.2 px.
Weight (CSS — font-weight: 600) 600 400 for a subtle inline loader; 700 for a headline-style loading screen.
Cycle duration (CSS — animation: 1.5s …) 1.5s 1s = fast dots (~250 ms per state); 2.4s = slow pace (~600 ms per state).
Timing uniformity (CSS — ease in animation) ease With ease, discrete content switches do not occur at strictly equal 375 ms intervals. Replace with steps(1, end) for a rigorously uniform rhythm.
Dot states (CSS — content values in @keyframes) "" / "." / ".." / "..." Replace with "•" / "••" / "•••" for middle dots, or reduce to two states ("" and "...") for a sharp on/off toggle.

FAQ

Yes: the animation declaration uses ease, a cubic Bézier curve. For content (a discrete property), the switch happens when the curve reaches 50% of progress over each 375 ms interval, which does not coincide with the half-duration mark. All four states remain visible, but their effective durations differ slightly. For a strictly uniform rhythm, replace ease with steps(1, end).
Glitch Text is a typographic effect: chromatic offset and clip-path on ::before/::after, communicating distortion or a retro aesthetic, not progress. Text Loading animates content on ::after to signal activity: the dot rhythm is a universal visual convention for waiting. Both effects target a text div, but they share neither the same render, nor the same use case, nor the same CSS technique.
Yes: leave the div's HTML content empty (<div class="loader-text"></div>). The animation will display "", ".", "..", "..." — bare indigo dots that look like a chat typing indicator. In that case, add aria-label="Loading" to the div: with no static text, screen readers have nothing to read without that attribute.