Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/docs/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export const NAVIGATION: NavigationGroup[] = [
name: 'Text Shimmer Wave',
href: '/docs/text-shimmer-wave',
},
{
name: 'Text Highlight',
href: '/docs/text-highlight',
},
],
},
{
Expand Down
111 changes: 111 additions & 0 deletions app/docs/text-highlight/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
export const metadata = {
title: 'Text Highlight - Motion-Primitives',
description:
'Animate a highlight effect on text with a background color. Built for React, Next.js, and Tailwind CSS.',
};

import { TextHighlightBasic } from './text-highlight-basic';
import { TextHighlightDirection } from './text-highlight-direction';
import { TextHighlightColor } from './text-highlight-color';
import { TextHighlightSpeed } from './text-highlight-speed';
import { TextHighlightAnimateOn } from './text-highlight-animate-on';
import ComponentCodePreview from '@/components/website/component-code-preview';

# Text Highlight

Animate a highlight effect on text with a background color. Customize the direction, color, speed, and trigger behavior.

## Examples

### Text Highlight Basic

<ComponentCodePreview
component={<TextHighlightBasic />}
filePath='app/docs/text-highlight/text-highlight-basic.tsx'
hasReTrigger
/>

### Direction

Control the direction of the highlight animation.

<ComponentCodePreview
component={<TextHighlightDirection />}
filePath='app/docs/text-highlight/text-highlight-direction.tsx'
hasReTrigger
/>

### Custom Colors

Customize the highlight color using Tailwind CSS classes.

<ComponentCodePreview
component={<TextHighlightColor />}
filePath='app/docs/text-highlight/text-highlight-color.tsx'
hasReTrigger
/>

### Speed

Control the animation speed. Higher values make the animation faster.

<ComponentCodePreview
component={<TextHighlightSpeed />}
filePath='app/docs/text-highlight/text-highlight-speed.tsx'
hasReTrigger
/>

### Animate On

Control when the highlight animation triggers: on mount, hover, or click.

<ComponentCodePreview
component={<TextHighlightAnimateOn />}
filePath='app/docs/text-highlight/text-highlight-animate-on.tsx'
/>

## Installation

<Tabs defaultValue="cli">

<TabsList>
<TabsTrigger value='cli'>CLI</TabsTrigger>
<TabsTrigger value='manual'>Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">

<InstallationCli value='text-highlight' />

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Copy and paste the following code into your project.</Step>

<CodeBlock filePath='components/core/text-highlight.tsx' />

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</Tabs>

## Component API

### TextHighlight

| Prop | Type | Default | Description |
| :------------- | :---------------------------- | :-------- | :--------------------------------------------------------------------------------------------------- |
| children | string | | The text content. |
| as | keyof JSX.IntrinsicElements | 'p' | The HTML tag to render, defaults to paragraph. |
| className | string | undefined | Optional CSS class for styling the component. |
| duration | number | undefined | The duration of the highlight animation in seconds. If not provided, speed is used. |
| speed | number | 1 | Animation speed multiplier. Higher values = faster animation. Used when duration is not provided. |
| highlightColor | string | undefined | Optional Tailwind CSS class for highlight color (e.g., 'bg-blue-300'). |
| direction | 'left' \| 'right' | 'right' | Direction of the highlight animation. 'right' = left to right, 'left' = right to left. |
| animateOn | 'click' \| 'hover' \| 'mount' | 'mount' | When to trigger the animation. 'mount' = on component mount, 'hover' = on hover, 'click' = on click. |
28 changes: 28 additions & 0 deletions app/docs/text-highlight/text-highlight-animate-on.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TextHighlight } from '@/components/core/text-highlight';

export function TextHighlightAnimateOn() {
return (
<div className='space-y-6'>
<div>
<p className='mb-2 text-sm text-zinc-600 dark:text-zinc-400'>
Hover to highlight:
</p>
<TextHighlight animateOn='hover'>Hover over this text</TextHighlight>
</div>
<div>
<p className='mb-2 text-sm text-zinc-600 dark:text-zinc-400'>
Click to toggle highlight:
</p>
<TextHighlight animateOn='click'>Click this text</TextHighlight>
</div>
<div>
<p className='mb-2 text-sm text-zinc-600 dark:text-zinc-400'>
Auto highlight on mount:
</p>
<TextHighlight animateOn='mount'>
This highlights automatically
</TextHighlight>
</div>
</div>
);
}
9 changes: 9 additions & 0 deletions app/docs/text-highlight/text-highlight-basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TextHighlight } from '@/components/core/text-highlight';

export function TextHighlightBasic() {
return (
<TextHighlight>
Highlight this text with an animated background
</TextHighlight>
);
}
23 changes: 23 additions & 0 deletions app/docs/text-highlight/text-highlight-color.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TextHighlight } from '@/components/core/text-highlight';

export function TextHighlightColor() {
return (
<div className='space-y-4'>
<div>
<TextHighlight highlightColor='bg-blue-300 dark:bg-blue-500'>
Blue highlight
</TextHighlight>
</div>
<div>
<TextHighlight highlightColor='bg-green-300 dark:bg-green-500'>
Green highlight
</TextHighlight>
</div>
<div>
<TextHighlight highlightColor='bg-pink-300 dark:bg-pink-500'>
Pink highlight
</TextHighlight>
</div>
</div>
);
}
18 changes: 18 additions & 0 deletions app/docs/text-highlight/text-highlight-direction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TextHighlight } from '@/components/core/text-highlight';

export function TextHighlightDirection() {
return (
<div className='space-y-4'>
<div>
<TextHighlight direction='right'>
Highlight from left to right
</TextHighlight>
</div>
<div>
<TextHighlight direction='left'>
Highlight from right to left
</TextHighlight>
</div>
</div>
);
}
17 changes: 17 additions & 0 deletions app/docs/text-highlight/text-highlight-speed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TextHighlight } from '@/components/core/text-highlight';

export function TextHighlightSpeed() {
return (
<div className='space-y-4'>
<div>
<TextHighlight speed={0.5}>Slow highlight (speed: 0.5)</TextHighlight>
</div>
<div>
<TextHighlight speed={1}>Normal highlight (speed: 1)</TextHighlight>
</div>
<div>
<TextHighlight speed={2}>Fast highlight (speed: 2)</TextHighlight>
</div>
</div>
);
}
89 changes: 89 additions & 0 deletions components/core/text-highlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client';
import React, { useState, type JSX } from 'react';
import { motion } from 'motion/react';
import { cn } from '@/lib/utils';

export type TextHighlightProps = {
children: string;
as?: React.ElementType;
className?: string;
duration?: number;
speed?: number;
highlightColor?: string;
direction?: 'left' | 'right';
animateOn?: 'click' | 'hover' | 'mount';
};

function TextHighlightComponent({
children,
as: Component = 'p',
className,
duration,
speed = 1,
highlightColor,
direction = 'right',
animateOn = 'mount',
}: TextHighlightProps) {
const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements
) as typeof motion.div;

const [isHighlighted, setIsHighlighted] = useState(animateOn === 'mount');

const defaultHighlightColor = 'bg-yellow-300 dark:bg-yellow-500';

// Calculate duration: if speed is provided, use it as a multiplier; otherwise use duration directly
const animationDuration = duration !== undefined ? duration : 1 / speed;

const transformOrigin = direction === 'right' ? 'left' : 'right';
const initialScaleX = direction === 'right' ? 0 : 0;
const animateScaleX = isHighlighted ? 1 : 0;

const handleClick = () => {
if (animateOn === 'click') {
setIsHighlighted(!isHighlighted);
}
};

const handleMouseEnter = () => {
if (animateOn === 'hover') {
setIsHighlighted(true);
}
};

const handleMouseLeave = () => {
if (animateOn === 'hover') {
setIsHighlighted(false);
}
};

const cursorStyle = animateOn === 'click' ? 'cursor-pointer' : '';

return (
<MotionComponent
className={cn('relative inline-block', cursorStyle, className)}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<span className='relative z-10'>{children}</span>
<motion.span
className={cn(
'absolute inset-0 z-0',
highlightColor || defaultHighlightColor
)}
initial={{ scaleX: initialScaleX }}
animate={{ scaleX: animateScaleX }}
transition={{
duration: animationDuration,
ease: 'easeInOut',
}}
style={{
transformOrigin,
}}
/>
</MotionComponent>
);
}

export const TextHighlight = React.memo(TextHighlightComponent);
7 changes: 7 additions & 0 deletions scripts/registry-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ export const components: ComponentDefinition[] = [
dependencies: ['motion'],
description: 'A component that creates a wave-like shimmer effect on text.',
},
{
name: 'text-highlight',
path: path.join(__dirname, '../components/core/text-highlight.tsx'),
registryDependencies: [],
dependencies: ['motion'],
description: 'A component that animates a highlight effect on text with a background color.',
},
{
name: 'magnetic',
path: path.join(__dirname, '../components/core/magnetic.tsx'),
Expand Down