Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Paint Text for React

Text you paint with the pointer: the letters it passes take a colour, keep it for a moment, then fade back. For React with Base UI, when a headline should invite a stroke of the pointer.

Framework

Paint Text lets the pointer paint a headline. Every letter the brush passes takes the paint colour at once and keeps it for a moment after the brush moves on, so a stroke leaves a trail of colour through the words that fades back, letter by letter, to the text's own colour.

Use it for a hero line, a playful heading or a call to action that rewards a sweep of the pointer. For a weight that follows the pointer, use Variable Proximity; for a highlighter mark that draws itself, Highlight Text. The letters' centres are measured once as the pointer enters; each move marks the letters inside the brush with a data attribute, and CSS does the rest: a quick colour transition paints them and, once a letter is unmarked, a delayed one fades it back. There's no animation frame loop, and nothing re-renders as the pointer moves.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { PaintText } from "@/components/ui/paint-text";
<h1>
  <PaintText text="Run the pointer through the words" />
</h1>

Demos

Colours

paintColor is the paint, any CSS colour, including your theme's variables such as var(--destructive). By default it's the theme's --chart-2.

Preview width

Brush

radius is the brush's size: letters whose centre is that close to the pointer, in pixels, take the paint. resetDelay is how long the paint stays on a letter before it fades, in milliseconds.

Preview width

Accessibility

Screen readers read the text once, from a visually hidden copy, never letter by letter. At rest the text is in its own colour, fully readable; choose a paintColor that also contrasts with the page, since painted letters are read too. The colour carries no meaning, so keyboard and touch users miss nothing; inside a link with keyboard focus the whole text shows painted. A colour fade isn't motion, so reduced motion keeps it.

FAQ

How long does the paint stay?

resetDelay after the brush leaves a letter, 1,200 ms by default, then it fades over --paint-text-fade, 900 ms. Pass a long resetDelay, a minute say, for paint that stays while the reader looks around.

How big is the brush?

radius sets it, 48 pixels by default: every letter whose centre is that close to the pointer takes the paint. A larger brush paints whole words at once; a smaller one, letter by letter.

Why --chart-2 by default?

The paint has to stand apart from the text in light and dark mode, and many themes' primary colour is the text colour itself. Every shadcn theme defines --chart-2 as a mid-tone. Pass any colour, such as var(--primary) when your primary is a colour.

Does it run on every frame?

No. It measures the letters once as the pointer comes over the text; each move then compares the pointer with every letter's centre and marks the ones under the brush. The colour change and the fade are CSS transitions. Nothing runs while the pointer is elsewhere.

What happens on a touch screen?

A tap paints the letters around it, and they fade back after resetDelay.

What happens with reduced motion?

Nothing changes: the paint only changes colour, which isn't motion, so it goes on and fades as usual.

API Reference

PaintText

<span> data-slot="paint-text"

PropType
textstring

The text.

paintColor?string

The paint's colour, any CSS colour. @default "var(--chart-2)".

radius?number

The brush's size: letters this close to the pointer take the paint, in pixels. @default 48.

Default
48
resetDelay?number

How long the paint stays on a letter before it fades, in milliseconds. @default 1200.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Forwarded to the part.

CSS variableType
--paint-text-colorstring

The paint's colour (var(--chart-2)).

--paint-text-fadestring

How long the paint takes to fade (900ms).

--paint-text-holdstring

How long the paint stays before it fades (1200ms).

  • Pro

    Erase Text

    Interactive

    Letters that fade under the pointer

  • Pro

    Outline Text

    Decorative

    Hollow letters that flood with colour

  • Every detail deserves the spotlight

    Pro

    Spotlight Text

    Interactive

    Light that follows the pointer

  • Pro

    An outline lit in a gradient

  • Highlight Text

    Decorative

    A highlighter band drawn across

  • Pro

    Annotation Text

    Decorative

    A pen stroke drawn round a word

All 97 text effects