) {
- const Comp = as ?? 'span'
- return (
-
- {children}
-
- )
-}
diff --git a/apps/sim/components/ui/thinking-loader.module.css b/apps/sim/components/ui/thinking-loader.module.css
index 52703157b5e..c63b730fa36 100644
--- a/apps/sim/components/ui/thinking-loader.module.css
+++ b/apps/sim/components/ui/thinking-loader.module.css
@@ -79,12 +79,9 @@
white-space: nowrap;
}
-/* The sweeping-band treatment itself (gradient, timing, dark mode, reduced
- motion) is owned by the shared shimmer-text module; this class only adds the
- loader-scaled font sizing. Canonical normal weight per emcn rules: body text
- is 400, never medium. */
+/* ShimmerText owns the gradient, timing, dark mode, and reduced motion. This
+ class only adds loader-scaled font sizing and canonical normal weight. */
.label {
- composes: shimmer from "./shimmer-text.module.css";
font-size: var(--tl-label-size, 14px);
font-weight: 400;
}
diff --git a/apps/sim/components/ui/thinking-loader.tsx b/apps/sim/components/ui/thinking-loader.tsx
index 660a9d476ad..4d93f1c9c62 100644
--- a/apps/sim/components/ui/thinking-loader.tsx
+++ b/apps/sim/components/ui/thinking-loader.tsx
@@ -1,7 +1,7 @@
'use client'
import { type CSSProperties, type ReactNode, useEffect, useId, useState } from 'react'
-import { cn } from '@sim/emcn'
+import { cn, ShimmerText } from '@sim/emcn'
import styles from '@/components/ui/thinking-loader.module.css'
const VARIANTS = [
@@ -532,11 +532,19 @@ export function ThinkingLoader({
{exitingLabel ? (
- {exitingLabel}
+ {shimmer ? (
+ {exitingLabel}
+ ) : (
+ {exitingLabel}
+ )}
) : null}
- {shownLabel}
+ {shimmer ? (
+ {shownLabel}
+ ) : (
+ {shownLabel}
+ )}
diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts
index 975d060b136..03802b0b6b2 100644
--- a/packages/emcn/src/components/index.ts
+++ b/packages/emcn/src/components/index.ts
@@ -218,6 +218,7 @@ export { ProgressItem } from './progress-item/progress-item'
export { RowActions, type RowActionsProps, rowActionsGroupClass } from './row-actions/row-actions'
export { SecretInput } from './secret-input/secret-input'
export { SecretReveal } from './secret-reveal/secret-reveal'
+export { ShimmerText, type ShimmerTextProps } from './shimmer-text/shimmer-text'
export { WORDMARK_PATHS, WORDMARK_VIEW_BOX } from './sim-wordmark/paths'
export { SimWordmark, type SimWordmarkProps } from './sim-wordmark/sim-wordmark'
export { Skeleton } from './skeleton/skeleton'
diff --git a/apps/sim/components/ui/shimmer-text.module.css b/packages/emcn/src/components/shimmer-text/shimmer-text.module.css
similarity index 94%
rename from apps/sim/components/ui/shimmer-text.module.css
rename to packages/emcn/src/components/shimmer-text/shimmer-text.module.css
index fffd7322138..45ab55fbcb7 100644
--- a/apps/sim/components/ui/shimmer-text.module.css
+++ b/packages/emcn/src/components/shimmer-text/shimmer-text.module.css
@@ -1,7 +1,7 @@
/**
* Claude-style text shimmer: the text paints from a gradient with a light band
* that sweeps across the glyphs via background-clip. This is the single source
- * of truth for the treatment — the ThinkingLoader label composes it. Under
+ * of truth for the treatment, including the ThinkingLoader label. Under
* reduced motion the sweep is replaced by a gentle opacity pulse in solid ink:
* the shimmer conveys essential in-progress state, so it needs a vestibular-safe
* fallback rather than none. Consumers whose resting text is not body ink set
diff --git a/packages/emcn/src/components/shimmer-text/shimmer-text.test.tsx b/packages/emcn/src/components/shimmer-text/shimmer-text.test.tsx
new file mode 100644
index 00000000000..6a673535253
--- /dev/null
+++ b/packages/emcn/src/components/shimmer-text/shimmer-text.test.tsx
@@ -0,0 +1,19 @@
+import { renderToStaticMarkup } from 'react-dom/server'
+import { describe, expect, it } from 'vitest'
+import { ShimmerText } from './shimmer-text'
+import styles from './shimmer-text.module.css'
+
+describe('ShimmerText', () => {
+ it('preserves the requested element, consumer classes, and native attributes', () => {
+ const html = renderToStaticMarkup(
+
+ Writing a reply
+
+ )
+ expect(html).toMatch(/^')
+ })
+})
diff --git a/packages/emcn/src/components/shimmer-text/shimmer-text.tsx b/packages/emcn/src/components/shimmer-text/shimmer-text.tsx
new file mode 100644
index 00000000000..37495f79ee7
--- /dev/null
+++ b/packages/emcn/src/components/shimmer-text/shimmer-text.tsx
@@ -0,0 +1,30 @@
+import type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
+import { cn } from '../../lib/cn'
+import styles from './shimmer-text.module.css'
+
+export type ShimmerTextProps = {
+ as?: T
+ children: ReactNode
+ className?: string
+} & Omit, 'as' | 'children' | 'className'>
+
+/**
+ * Sweeping-highlight shimmer for active text. Size and weight come from the
+ * consumer; `--shimmer-rest` supplies resting ink under reduced motion.
+ *
+ * @example
+ * Working…
+ */
+export function ShimmerText({
+ as,
+ children,
+ className,
+ ...props
+}: ShimmerTextProps) {
+ const Comp = as ?? 'span'
+ return (
+
+ {children}
+
+ )
+}