Kosmesis
Components

Shimmer

A pure-CSS shimmer sweep for text. Purely presentational — no Morphos equivalent.

Installation

npx kosmesis add shimmer
pnpm dlx kosmesis add shimmer
yarn dlx kosmesis add shimmer
bunx kosmesis add shimmer

Copy and paste the following code into your project.

shimmer.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";let shimmerIdCounter = 0;export interface ShimmerProps {  color?: string;  duration?: number;  spread?: number | string;  angle?: number;  reverse?: boolean;  once?: boolean;  disabled?: boolean;  class?: string;  children?: Children;}// Transparency is `-webkit-text-fill-color`, not `color` — `currentColor` in the gradient// resolves against this element's own `color`, so setting `color` would zero out the gradient.// `animation-*` stays in the stylesheet (read via `var()`) rather than inline, so `:dir(rtl)` and// reduced-motion below can override it without fighting inline-style specificity.@Component()export class Shimmer extends StatelessComponent<ShimmerProps> {  private readonly _id = shimmerIdCounter++;  private readonly _animName = `kosmesis-shimmer-${String(this._id)}`;  private readonly _scopeClass = `kosmesis-shimmer-scope-${String(this._id)}`;  render() {    const {      color,      duration = 2000,      spread = "calc(3ch + 40px)",      angle = 20,      reverse = false,      once = false,      disabled = false,      class: cls,      children,    } = this.props;    if (disabled) {      return (        <span data-slot="shimmer" class={cls}>          {children}        </span>      );    }    const spreadValue = typeof spread === "number" ? `${String(spread)}px` : spread;    return (      <span        data-slot="shimmer"        data-shimmer-reverse={reverse ? "" : undefined}        class={cn(this._scopeClass, cls)}        style={{          "--shimmer-anim-name": this._animName,          "--shimmer-color": color ?? "oklch(from currentColor calc(l + 0.35) c h)",          "--shimmer-spread": spreadValue,          "--shimmer-angle": `${String(angle)}deg`,          "--shimmer-duration": `${String(duration)}ms`,          "--shimmer-iteration": once ? "1" : "infinite",          "--shimmer-fill": once ? "forwards" : "none",        }}      >        <style>{`@keyframes ${this._animName} { from { background-position: 200% 0, 0 0; } to { background-position: -200% 0, 0 0; } }.${this._scopeClass} {  background-image: linear-gradient(var(--shimmer-angle), transparent calc(50% - var(--shimmer-spread) / 2), var(--shimmer-color), transparent calc(50% + var(--shimmer-spread) / 2)), linear-gradient(currentColor, currentColor);  background-size: 250% 100%, 100% 100%;  background-repeat: no-repeat;  background-position: 200% 0, 0 0;  -webkit-background-clip: text;  background-clip: text;  -webkit-text-fill-color: transparent;  animation-name: var(--shimmer-anim-name);  animation-duration: var(--shimmer-duration);  animation-timing-function: linear;  animation-iteration-count: var(--shimmer-iteration, infinite);  animation-fill-mode: var(--shimmer-fill, none);  animation-direction: normal;}.${this._scopeClass}[data-shimmer-reverse] { animation-direction: reverse; }.${this._scopeClass}:dir(rtl) { animation-direction: reverse; }.${this._scopeClass}[data-shimmer-reverse]:dir(rtl) { animation-direction: normal; }@media (prefers-reduced-motion: reduce) {  .${this._scopeClass} {    animation: none;    background-image: none;    -webkit-background-clip: initial;    background-clip: initial;    -webkit-text-fill-color: inherit;  }}`}</style>        {children}      </span>    );  }}

Install the following dependencies:

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

shimmer.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";let shimmerIdCounter = 0;// Transparency is `-webkit-text-fill-color`, not `color` — `currentColor` in `backgroundImage`// resolves against this element's own `color`, so setting `color` would zero out the gradient.// `animation-*` stays here (read via `var()`) rather than inline, so `:dir(rtl)` and reduced-motion// below can override it without fighting inline-style specificity.class ShimmerStyles extends Stylesheet {  $root = this.css({    backgroundImage:      "linear-gradient(var(--shimmer-angle), transparent calc(50% - var(--shimmer-spread) / 2), var(--shimmer-color), transparent calc(50% + var(--shimmer-spread) / 2)), linear-gradient(currentColor, currentColor)",    backgroundSize: "250% 100%, 100% 100%",    backgroundRepeat: "no-repeat",    backgroundPosition: "200% 0, 0 0",    WebkitBackgroundClip: "text",    backgroundClip: "text",    WebkitTextFillColor: "transparent",    animationName: "var(--shimmer-anim-name)",    animationDuration: "var(--shimmer-duration)",    animationTimingFunction: "linear",    animationIterationCount: "var(--shimmer-iteration, infinite)",    animationFillMode: "var(--shimmer-fill, none)",    animationDirection: "normal",  })    .on("&[data-shimmer-reverse]", { animationDirection: "reverse" })    .on("&:dir(rtl)", { animationDirection: "reverse" })    .on("&[data-shimmer-reverse]:dir(rtl)", { animationDirection: "normal" })    .media("(prefers-reduced-motion: reduce)", {      animation: "none",      backgroundImage: "none",      WebkitBackgroundClip: "initial",      backgroundClip: "initial",      WebkitTextFillColor: "inherit",    });}export interface ShimmerProps {  color?: string;  duration?: number;  spread?: number | string;  angle?: number;  reverse?: boolean;  once?: boolean;  disabled?: boolean;  class?: string;  children?: Children;}@Component()export class Shimmer extends StatelessComponent<ShimmerProps> {  @Styled(ShimmerStyles) $s!: ShimmerStyles;  private readonly _animName = `kosmesis-shimmer-${String(shimmerIdCounter++)}`;  render() {    const {      color,      duration = 2000,      spread = "calc(3ch + 40px)",      angle = 20,      reverse = false,      once = false,      disabled = false,      class: cls,      children,    } = this.props;    if (disabled) {      return (        <span data-slot="shimmer" class={cls}>          {children}        </span>      );    }    const spreadValue = typeof spread === "number" ? `${String(spread)}px` : spread;    return (      <span        data-slot="shimmer"        data-shimmer-reverse={reverse ? "" : undefined}        class={cx(this.$s.$root, cls)}        style={{          "--shimmer-anim-name": this._animName,          "--shimmer-color": color ?? "oklch(from currentColor calc(l + 0.35) c h)",          "--shimmer-spread": spreadValue,          "--shimmer-angle": `${String(angle)}deg`,          "--shimmer-duration": `${String(duration)}ms`,          "--shimmer-iteration": once ? "1" : "infinite",          "--shimmer-fill": once ? "forwards" : "none",        }}      >        <style>{`@keyframes ${this._animName} { from { background-position: 200% 0, 0 0; } to { background-position: -200% 0, 0 0; } }`}</style>        {children}      </span>    );  }}

Examples

About

Purely presentational — no Morphos equivalent. Unlike shadcn's shimmer (a set of global Tailwind utility classes shipped via the shadcn npm package), Kosmesis ships this as a copy-paste Shimmer component: wrap any text in it instead of adding a shimmer class. The effect is pure CSS — background-clip: text paints the text with a moving highlight gradient layered over a static currentColor gradient, so it adapts to whatever color the wrapping element sets, in light or dark mode, with no configuration needed. Every prop (color, duration, spread, angle) is threaded through as a CSS custom property and read back via var() in a scoped rule injected once per instance — the actual animation-*/background-* properties are never set inline, so the RTL and reduced-motion overrides in that same rule always win over the per-instance custom properties.

<p className="text-muted-foreground">
  <Shimmer>Generating response&hellip;</Shimmer>
</p>

With Marker

A common pairing is a status dot and spinner next to shimmering status text:

<div className="flex items-center gap-2">
  <Marker variant="default" aria-label="Working" />
  <Spinner />
  <Shimmer>Thinking&hellip;</Shimmer>
</div>

Color

Use the color prop to set the highlight color explicitly — any valid CSS color, including color-mix()/alpha syntax for opacity. Left unset, the highlight is derived automatically from currentColor via relative color syntax (oklch(from currentColor calc(l + 0.35) c h)), so it adapts to any text color without configuration.

<Shimmer color="oklch(0.7 0.19 260 / 70%)">Generating response&hellip;</Shimmer>
<Shimmer color="#378ADD">Generating response&hellip;</Shimmer>

Duration

Use duration to set the length of one sweep in milliseconds. The default is 2000.

<Shimmer duration={1000}>Generating response&hellip;</Shimmer>

Spread

Use spread to set the width of the highlight band, as a number of pixels or any CSS length string. The default is "calc(3ch + 40px)": a fixed base plus a 3ch term that scales with the font size.

<Shimmer spread={96}>Generating response&hellip;</Shimmer>
<Shimmer spread="5rem">Generating response&hellip;</Shimmer>

Angle

Use angle to set the tilt of the highlight band, in degrees. The default is 20.

<Shimmer angle={45}>Generating response&hellip;</Shimmer>

Reverse

Use reverse to sweep the highlight in the opposite direction. In RTL layouts the sweep already follows the reading direction — see RTL.

<Shimmer reverse>Generating response&hellip;</Shimmer>

Play Once

Use once to play a single sweep instead of looping — useful as a reveal when streaming completes. Pair it with duration to control how long the sweep takes.

<Shimmer duration={1100} once>
  Response generated.
</Shimmer>

Disabled

Use disabled to turn the effect off and render the text normally — the component skips every shimmer style entirely, so it's cheap to gate behind a prop.

<Shimmer disabled={!isStreaming}>Generating response&hellip;</Shimmer>

Fallback

The shimmer is built on modern color features — relative color syntax and multi-layer background-clip: text — available in all current browsers. In older browsers without support, the highlight gradient is dropped and the text can render transparent; pass an explicit color (a plain color, not relative-color syntax) if you need to support them.

Reduced Motion

When the user prefers reduced motion, the animation and gradient are disabled automatically and the text renders as normal currentColor text. There is nothing to configure.

RTL

The sweep follows the reading direction — left to right in LTR, right to left in RTL — with no extra props, driven by :dir(rtl) in the component's own scoped rule. Use reverse to flip the direction manually in either mode.

Props

PropTypeDefault
colorstringauto (brightened currentColor)
durationnumber (ms)2000
spreadnumber (px) | string"calc(3ch + 40px)"
anglenumber (deg)20
reversebooleanfalse
oncebooleanfalse
disabledbooleanfalse
classstring

On this page