Pulsing Text
A component that applies a subtle, rhythmic pulse animation to its children.
Default
The default component applies a subtle scale animation.
Variants
The component has two visual variants: scale
(default) and opacity
.
Dynamic Control
You can dynamically control the animation's duration
and intensity
using props. This example uses sliders to demonstrate the effect in real-time.
Use Cases & Composition
Use the iterationCount
prop for finite animations, like notifications. Use the asChild
prop to apply the pulse effect to other components like Button
or Card
.
Installation
CLI
Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.
Manual
Install the following dependencies:
npm install @radix-ui/react-slot class-variance-authority
Then, copy and paste the following code into your project:
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const pulsingTextVariants = cva('inline-block origin-center', {
variants: {
variant: {
scale: 'animate-pulse-scale',
opacity: 'animate-pulse-opacity',
},
},
defaultVariants: {
variant: 'scale',
},
});
type PulsingTextCssProperties = {
'--pulse-duration'?: string;
'--pulse-intensity-scale'?: number;
'--pulse-intensity-opacity'?: number;
'--pulse-delay'?: string;
'--pulse-iteration-count'?: number | 'infinite';
};
type CombinedCssProperties = React.CSSProperties & PulsingTextCssProperties;
export interface PulsingTextProps
extends React.HTMLAttributes<HTMLSpanElement>,
VariantProps<typeof pulsingTextVariants> {
/**
* If true, the component will merge its props and behavior onto its immediate child.
* This is useful for applying the pulse effect to other components or elements.
* @default false
*/
asChild?: boolean;
/**
* The duration of one full pulse cycle in seconds.
* @default 2
*/
duration?: number;
/**
* A normalized value from 0 to 1 that controls the strength of the pulse effect.
* @default 0.5
*/
intensity?: number;
/**
* The delay before the animation starts, in seconds.
* @default 0
*/
delay?: number;
/**
* The number of times the animation should repeat.
* Use 'infinite' for a never-ending loop.
* @default 'infinite'
*/
iterationCount?: number | 'infinite';
/**
* Allows passing a standard style object, fully typed to include the animation's
* controlling CSS Custom Properties.
*/
style?: CombinedCssProperties;
}
/**
* A dynamic, performant server component that applies a subtle, rhythmic pulse animation.
* It is controlled via props that are translated to CSS Custom Properties, allowing for
* a wide range of visual effects without client-side JavaScript.
*/
const PulsingText = React.forwardRef<HTMLSpanElement, PulsingTextProps>(
(
{
className,
style,
asChild = false,
variant = 'scale',
duration,
intensity,
delay,
iterationCount,
...props
},
ref
) => {
const Comp = asChild ? Slot : 'span';
// WHY: Clamp the intensity to a 0-1 range to ensure predictable and safe animation values.
const clampedIntensity =
intensity === undefined ? undefined : Math.max(0, Math.min(1, intensity));
const dynamicStyles: CombinedCssProperties = {
...style,
};
if (duration !== undefined) {
dynamicStyles['--pulse-duration'] = `${duration}s`;
}
if (clampedIntensity !== undefined) {
if (variant === 'scale') {
dynamicStyles['--pulse-intensity-scale'] = 1 + clampedIntensity * 0.1;
} else {
dynamicStyles['--pulse-intensity-opacity'] =
1 - clampedIntensity * 0.25;
}
}
if (delay !== undefined) {
dynamicStyles['--pulse-delay'] = `${delay}s`;
}
if (iterationCount !== undefined) {
dynamicStyles['--pulse-iteration-count'] = iterationCount;
}
return (
<Comp
className={cn(pulsingTextVariants({ variant, className }))}
style={dynamicStyles}
ref={ref}
{...props}
/>
);
}
);
PulsingText.displayName = 'PulsingText';
export { PulsingText };
Usage
Import the component and use it to wrap text or other components.
import { Button } from '@/components/ui/button';
import { PulsingText } from '@/components/ui/pulsing-text';
export default function Example() {
return (
<div className='flex flex-col items-center gap-8'>
{/* Default usage */}
<PulsingText className='text-xl font-semibold'>
Limited Time Offer
</PulsingText>
{/* Customized usage on a button */}
<PulsingText asChild variant='opacity' duration={1.5} intensity={0.8}>
<Button>Get Started Now</Button>
</PulsingText>
</div>
);
}
Props
Prop | Type | Default | Description |
---|---|---|---|
asChild | boolean | false | If true, the component will merge its props and behavior onto its immediate child. |
duration | number | 2 | The duration of one full pulse cycle in seconds. |
intensity | number | 0.5 | A normalized value from 0 to 1 that controls the strength of the pulse effect. |
delay | number | 0 | The delay before the animation starts, in seconds. |
iterationCount | number | 'infinite' | 'infinite' | The number of times the animation should repeat. |
variant | 'scale' | 'opacity' | 'scale' | The type of pulse animation to apply. |
style | React.CSSProperties | — | Allows passing a standard style object, including the animation's controlling CSS Custom Properties. |