Skip to content
WANCRAFT

C.192Typography & Text

Text Search Highlighter

Find-in-text for any content block: every match highlighted, a "3 of 12" counter, and Enter / Shift+Enter to step through them.

NewReactCSSTailwindadvanced

Overview

Text Search Highlighter wraps any React content with a find bar, the way a browser's Find works but scoped to one block. It searches the rendered text — across inline elements like <em> and <strong>, but never across paragraphs — and highlights every match, with the current one styled distinctly. Case and diacritics are ignored by default, so "cafe" finds "Café", and each option, plus whole-word matching, has its own toggle. Enter and Shift+Enter (or the arrow buttons) move between matches and scroll the current one to the middle of the content area by setting its scrollTop, so the page itself never jumps. Where the CSS Custom Highlight API is supported it paints highlights without touching the DOM. Elsewhere it falls back to <mark> elements placed in sibling clones, which leaves React's own text nodes where they are and restores them exactly. A MutationObserver re-runs the search when the content changes, a polite live region announces "Match 3 of 12", and Escape clears the query.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/text-search-highlighter.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/text-search-highlighter.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/text-search-highlighter.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { TextSearchHighlighter } from "@/components/ui/text-search-highlighter";

export function TextSearchHighlighterExample() {
  return (
    <TextSearchHighlighter>
      <article className="space-y-3">
        <p>In Zürich the first café opened at seven.</p>
        <p>São Paulo was louder, and the cafe never closed.</p>
      </article>
    </TextSearchHighlighter>
  );
}

03Props

PropTypeDefaultDescription
childrenReactNode—Required. The content to search; only its rendered, visible text is searched.
labelstring"Find in text"Accessible name of the search field (and the content region).
placeholderstring"Find in text"Search field placeholder.
defaultQuerystring""Initial query.
matchCasebooleanfalseInitial state of the Match case toggle.
matchDiacriticsbooleanfalseInitial state of the Match diacritics toggle (off folds é → e).
wholeWordbooleanfalseInitial state of the Whole word toggle.
showOptionsbooleantrueShow the three option toggles.
engine"auto" | "marks""auto"Highlight API when supported, or always <mark> wrapping.
maxHeightnumber280Max height of the scrolling content area in px.
accentstring"#FF7448"Highlight colour (all matches use a 32% tint; the current one is solid).
onMatchesChange(count: number) => void—Called with the match count after every search.

04Features

  • 01Searches rendered text across inline elements, never across block boundaries
  • 02Case-, diacritic- and whole-word options with live toggles (Unicode NFD folding)
  • 03CSS Custom Highlight API when available — zero DOM changes — with a feature-detected <mark> fallback
  • 04The fallback keeps React's text nodes in place and restores them exactly
  • 05"3 of 12" counter, Enter / Shift+Enter and arrow buttons, wrapping at the ends
  • 06Scrolls the current match to the middle of its own container via scrollTop — the page never jumps
  • 07MutationObserver re-runs the search when React updates the content
input · keyboardinput · clickinput · focus

05Accessibility

  • The toolbar is a search landmark; the input has an accessible name and aria-controls pointing at the content region.
  • A polite live region announces "Match 3 of 12" or "No matches" as you type and step.
  • Enter and Shift+Enter step forward and back; Escape clears the query and is consumed only while there is a query.
  • Option toggles are buttons with aria-pressed and accessible names (Match case, Match diacritics, Whole word).
  • The scrolling content region is focusable, so it can be scrolled from the keyboard, and has a visible focus ring.
  • Highlights are colour plus a filled background; the current match is solid and dark-on-accent for contrast. With reduced motion the scroll is instant.

06Limitations

  • Stops counting at 1,000 matches.
  • Content hidden with display: none is skipped; text inside form fields is not searched.