Dotted Text
Text created entirely from dots arranged in a grid. Each letter is built with dots that can change colors and move around.
Install dependencies
npm i framer-motion
Dotted Text
Create a file dotted-text.tsx in your components folder and paste this code
'use client';
import { motion } from 'framer-motion';
// Define the dot matrix for each character
const charMatrix: Record<string, number[][]> = {
A: [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1]
],
B: [
[1, 1, 1, 0],
[1, 0, 0, 1],
[1, 1, 1, 0],
[1, 0, 0, 1],
[1, 1, 1, 0]
],
C: [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 0],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
D: [
[1, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 0]
],
E: [
[1, 1, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0],
[1, 0, 0, 0],
[1, 1, 1, 1]
],
F: [
[1, 1, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0],
[1, 0, 0, 0],
[1, 0, 0, 0]
],
G: [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 0],
[1, 0, 1, 1],
[0, 1, 1, 1]
],
H: [
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1]
],
I: [
[1, 1, 1],
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
[1, 1, 1]
],
J: [
[0, 0, 1, 1],
[0, 0, 0, 1],
[0, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
K: [
[1, 0, 0, 1],
[1, 0, 1, 0],
[1, 1, 0, 0],
[1, 0, 1, 0],
[1, 0, 0, 1]
],
L: [
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 1, 1]
],
M: [
[1, 0, 0, 0, 1],
[1, 1, 0, 1, 1],
[1, 0, 1, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1]
],
N: [
[1, 0, 0, 1],
[1, 1, 0, 1],
[1, 0, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1]
],
O: [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
P: [
[1, 1, 1, 0],
[1, 0, 0, 1],
[1, 1, 1, 0],
[1, 0, 0, 0],
[1, 0, 0, 0]
],
Q: [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 1, 1],
[0, 1, 1, 1]
],
R: [
[1, 1, 1, 0],
[1, 0, 0, 1],
[1, 1, 1, 0],
[1, 0, 1, 0],
[1, 0, 0, 1]
],
S: [
[0, 1, 1, 1],
[1, 0, 0, 0],
[0, 1, 1, 0],
[0, 0, 0, 1],
[1, 1, 1, 0]
],
T: [
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]
],
U: [
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
V: [
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 0, 1, 0]
],
W: [
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 1],
[1, 0, 0, 0, 1]
],
X: [
[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 0, 0, 0],
[0, 1, 1, 0],
[1, 0, 0, 1]
],
Y: [
[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0]
],
Z: [
[1, 1, 1, 1],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
[1, 1, 1, 1]
],
'0': [
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
'1': [
[0, 1, 0],
[1, 1, 0],
[0, 1, 0],
[0, 1, 0],
[1, 1, 1]
],
'2': [
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
[1, 1, 1, 1]
],
'3': [
[1, 1, 1, 0],
[0, 0, 0, 1],
[0, 1, 1, 0],
[0, 0, 0, 1],
[1, 1, 1, 0]
],
'4': [
[0, 0, 1, 1],
[0, 1, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 1],
[0, 0, 0, 1]
],
'5': [
[1, 1, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0],
[0, 0, 0, 1],
[1, 1, 1, 0]
],
'6': [
[0, 1, 1, 0],
[1, 0, 0, 0],
[1, 1, 1, 0],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
'7': [
[1, 1, 1, 1],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]
],
'8': [
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 1, 1, 0]
],
'9': [
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 1, 1, 1],
[0, 0, 0, 1],
[0, 1, 1, 0]
],
'.': [[0], [0], [0], [0], [1]],
',': [[0], [0], [0], [1], [1]],
'!': [[1], [1], [1], [0], [1]],
'?': [
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 0, 0],
[0, 0, 1, 0]
],
'+': [
[0, 0, 0],
[0, 1, 0],
[1, 1, 1],
[0, 1, 0],
[0, 0, 0]
],
'-': [
[0, 0, 0],
[0, 0, 0],
[1, 1, 1],
[0, 0, 0],
[0, 0, 0]
],
'*': [
[1, 0, 1],
[0, 1, 0],
[1, 0, 1],
[0, 0, 0],
[0, 0, 0]
],
'/': [
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 0, 0]
],
'=': [
[0, 0, 0],
[1, 1, 1],
[0, 0, 0],
[1, 1, 1],
[0, 0, 0]
],
' ': [
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]
]
};
// Default matrix for unknown characters
const defaultMatrix = [
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1]
];
interface DottedTextProps {
text: string;
color?: string;
backgroundColor?: string;
size?: number;
spacing?: number;
animationDelay?: number;
animationColors?: string[];
animationDuration?: number;
shadowIntensity?: number;
}
const DottedText: React.FC<DottedTextProps> = ({
text,
size = 10,
spacing = 2,
animationDelay = 0.3,
animationColors = [
'#1A0B33', // Deeper purple (twilight)
'#5D2E8C', // Purple
'#F25C54', // Coral/orange
'#F7B267', // Golden orange
'#FFD166', // Amber/gold
'#FFEDDF', // Soft light
'#F7B267', // Golden orange
'#F25C54', // Coral/orange
'#5D2E8C', // Purple
'#1A0B33' // Deeper purple (twilight)
],
animationDuration = 4,
shadowIntensity = 15
}) => {
const renderDot = (
filled: boolean,
x: number,
y: number,
animationIndex: number,
repeatDelay: number
) => {
const dotSize = `${size}px`;
if (!filled) {
return (
<div
key={`${x}-${y}`}
style={{
width: dotSize,
height: dotSize,
margin: `${spacing}px`,
display: 'inline-block'
}}
/>
);
}
return (
<motion.div
key={`${x}-${y}`}
style={{
width: dotSize,
height: dotSize,
borderRadius: '50%',
border: '1px solid #FFFFFF47',
margin: `${spacing}px`,
display: 'inline-block',
backgroundColor: animationColors[0]
}}
initial={{
backgroundColor: animationColors[0],
boxShadow: 'none'
}}
animate={{
backgroundColor: animationColors,
boxShadow: [
'none',
'none',
`0 0 ${shadowIntensity / 2.1}px 0 ${animationColors[2]}`,
`0 0 ${shadowIntensity / 2}px 0 ${animationColors[3]}`,
`0 0 ${shadowIntensity}px 0 ${animationColors[4]}`,
`0 0 ${shadowIntensity}px 0 ${animationColors[5]}`,
`0 0 ${shadowIntensity / 2}px 0 ${animationColors[6]}`,
`0 0 ${shadowIntensity / 2.1}px 0 ${animationColors[7]}`,
'none',
'none'
]
}}
transition={{
duration: animationDuration,
delay: animationIndex * animationDelay,
repeat: Infinity,
repeatType: 'reverse',
repeatDelay: repeatDelay,
ease: 'easeInOut'
}}
/>
);
};
const renderCharacter = (char: string, charIndex: number, totalChars: number) => {
const upperChar = char.toUpperCase();
const matrix = charMatrix[upperChar] || defaultMatrix;
return (
<div
key={charIndex}
style={{
display: 'inline-block',
marginRight: `${spacing * 3}px`,
verticalAlign: 'top'
}}
>
{matrix.map((row, rowIndex) => (
<div key={rowIndex} style={{ display: 'flex' }}>
{row.map((dot, dotColIndex) =>
renderDot(
dot === 1,
charIndex * (matrix[0].length + 2) + dotColIndex,
rowIndex,
charIndex * 4 + dotColIndex,
totalChars * 0.8
)
)}
</div>
))}
</div>
);
};
return (
<div className="flex flex-wrap gap-2 relative">
{text.split('').map((char, index) => renderCharacter(char, index, text.length))}
</div>
);
};
export default DottedText;
Usage
<DottedText text="LIONEL MESSI" />