Shimmer
A flashy, animated text input with Mystral-style shimmer that cycles through gold-to-red colors while showing dynamic “thinking → generating → done” states inspired by Mistral Ai.
Loading demo...
"use client";
import React, { useMemo, type JSX } from "react";
import { AnimatePresence, motion } from "motion/react";
import { cn } from "@/lib/utils";
export type TextShimmerProps = {
children: React.ReactNode;
as?: React.ElementType;
className?: string;
duration?: number;
spread?: number;
};
function TextShimmerComponent({
children,
as: Component = "span",
className,
duration = 2,
spread = 2,
}: TextShimmerProps) {
const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements,
);
const dynamicSpread = useMemo(() => {
const length =
typeof children === "string"
? children.length
: React.Children.count(children);
return length * spread;
}, [children, spread]);
return (
<MotionComponent
className={cn(
"relative inline-block bg-size-[300%_100%,auto] bg-clip-text",
"text-transparent [--base-color:#FFD700]",
"[background-repeat:no-repeat,padding-box]",
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),#FFD700,#FFAF00,#FF8205,#FA500F,#E10500,#0000_calc(50%+var(--spread)))]",
className,
)}
key={String(children)}
initial={{ backgroundPosition: "100% center" }}
animate={{ backgroundPosition: "0% center" }}
transition={{
repeat: Infinity,
duration,
ease: "linear",
}}
style={
{
"--spread": `${dynamicSpread}px`,
backgroundImage:
"var(--bg), linear-gradient(var(--base-color), var(--base-color))",
} as React.CSSProperties
}
>
{children}
</MotionComponent>
);
}
const TextShimmer = React.memo(TextShimmerComponent);
type State = "thinking" | "generating" | "done";
const states: Record<State, string> = {
thinking: "Thinking...",
generating: "Generating...",
done: "Done",
};
const MystralShimmer = () => {
const [value, setValue] = React.useState("");
const [state, setState] = React.useState<State>("thinking");
const [loading, setLoading] = React.useState(false);
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setValue("");
setState("thinking");
setTimeout(() => {
setState("generating");
setTimeout(() => {
setState("done");
setLoading(false);
}, 4000);
}, 4000);
};
// mystral shimmer colors FFD700 FFAF00 FF8205 FA500F E10500
return (
<div className="size-full bg-background flex items-start justify-end overflow-visible">
<div className="border rounded-bl-lg size-[80%] border-r-0 bg-muted border-t-0 p-2 pr-0 flex flex-col justify-end">
<form onSubmit={onSubmit} className="h-10 w-full relative flex">
<div className="size-full border-r-0 border overflow-hidden rounded-l-md flex">
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
className="flex-1 px-4 bg-background z-10 outline-none text-muted-foreground disabled:cursor-not-allowed"
placeholder="> Ask anything..."
autoFocus
disabled={loading}
/>
<AnimatePresence initial={false}>
{loading && (
<motion.div
className="absolute left-4 bottom-full overflow-visible"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: -8 }}
exit={{ opacity: 0, y: 4 }}
transition={{ duration: 0.3 }}
>
<AnimatePresence mode="popLayout">
<motion.div
key={state}
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{ duration: 0.25, ease: "easeOut" }}
>
<TextShimmer>
{states[state].split("").map((char, index) => (
<motion.span
key={`${state}-${index}`}
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{
duration: 0.2,
delay: index * 0.02,
}}
className="inline-block"
>
{char}
</motion.span>
))}
</TextShimmer>
</motion.div>
</AnimatePresence>
</motion.div>
)}
</AnimatePresence>
</div>
</form>
</div>
</div>
);
};
export default MystralShimmer;