typescriptbeginner

Search with Text Highlighting

Build a search component that highlights matching text within results using safe HTML rendering.

typescript
import { useMemo, useState } from 'react';

// Safely highlight matching text
function HighlightedText({ text, query }: { text: string; query: string }) {
  if (!query.trim()) return <span>{text}</span>;

  const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const regex = new RegExp(`(${escaped})`, 'gi');
  const parts = text.split(regex);

  return (
    <span>
      {parts.map((part, i) =>
        regex.test(part) ? (
          <mark key={i} className="bg-yellow-500/30 text-yellow-200 rounded px-0.5">
            {part}
          </mark>
        ) : (
          <span key={i}>{part}</span>
        ),
      )}
    </span>
  );
}

// Search list component
interface SearchItem {
  id: string;
  title: string;
  description: string;
}

function SearchList({ items }: { items: SearchItem[] }) {
  const [query, setQuery] = useState('');

  const filtered = useMemo(() => {
    if (!query.trim()) return items;
    const lower = query.toLowerCase();
    return items.filter(
      (item) =>
        item.title.toLowerCase().includes(lower) ||
        item.description.toLowerCase().includes(lower),
    );
  }, [items, query]);

  return (
    <div className="space-y-3">
      <input
        type="search"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
        className="w-full bg-[#111] border border-white/10 rounded-lg px-4 py-2
          text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500"
      />
      <p className="text-gray-500 text-sm">
        {filtered.length} of {items.length} results
      </p>
      <ul className="space-y-2">
        {filtered.map((item) => (
          <li
            key={item.id}
            className="bg-[#111] border border-white/10 rounded-xl p-3
              hover:bg-[#161616] hover:border-white/20 transition-colors"
          >
            <h3 className="text-white font-medium">
              <HighlightedText text={item.title} query={query} />
            </h3>
            <p className="text-gray-400 text-sm mt-1">
              <HighlightedText text={item.description} query={query} />
            </p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export { HighlightedText, SearchList };

Use Cases

  • Search results display
  • Filtered lists with highlights
  • Documentation search

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.