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-authorityThen, 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. |