npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-highlight-selector

v1.0.9

Published

Text highlighter with customizable palette and text copier

Readme

react-highlight-selector

Text highlighter for React with a customizable color palette, popover actions, and copy support. Select any text inside rendered HTML to highlight it, recolor it, copy it, or remove it — and serialize selections so they can be saved and re-rendered later.

npm version license

Features

  • Highlight any text selection inside HTML content with a mouse drag
  • Built-in popover with color palette, delete, and copy actions
  • Custom popover support — bring your own popover component
  • Multi-instance — run several independent highlighters on one page via identifier
  • Serializable selections — selections are stored with xpath-range metadata, so you can persist them and rehydrate highlights later from the saved HTML
  • Selection constraints — enforce min/max selection length
  • TypeScript — fully typed API

Installation

npm install react-highlight-selector
# or
yarn add react-highlight-selector
# or
pnpm add react-highlight-selector

Requires React 18+ (react and react-dom as peer dependencies).

Quick start

Wrap your app (or the relevant subtree) in SelectionProvider, then render a Highlighter with an HTML string and a unique identifier:

import { SelectionProvider, Highlighter } from 'react-highlight-selector'

const html = `<p>The quick brown fox jumps over the lazy dog. 
Select any part of this text to highlight it.</p>`

function App() {
  return (
    <SelectionProvider>
      <Highlighter
        identifier="doc-1"
        htmlString={html}
        onSelection={(selection) => console.log('highlighted:', selection)}
      />
    </SelectionProvider>
  )
}

export default App

Drag-select text inside the rendered content — it gets wrapped in a highlight. Hover a highlight to open the popover: change color, copy the text, or delete the highlight.

Styling

Highlights are plain <span> elements whose appearance is driven by CSS class names. The default wrapper class is bg-lightgreen relative select-none, and the default popover swaps in bg-red, bg-yellow, and bg-blue. Define these classes in your stylesheet (or use Tailwind utilities):

.bg-lightgreen { background-color: #15f5ba; }
.bg-red        { background-color: #ff407d; }
.bg-yellow     { background-color: #f5dd61; }
.bg-blue       { background-color: #59d5e0; }

.relative      { position: relative; }
.select-none   { user-select: none; }

/* Popover positioning (popover is rendered inside the highlight span) */
.popover {
  position: absolute;
  bottom: 100%;
  left: 0;
  z-index: 10;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.15s ease;
}

You can override the highlight look per-instance with selectionWrapperClassName.

Examples

Callbacks: selection, copy, and highlight click

<Highlighter
  identifier="article"
  htmlString={articleHtml}
  minSelectionLength={3}
  maxSelectionLength={500}
  onSelection={(selection) => {
    // fired once when the user finishes a new highlight
    console.log(selection.text, selection.meta)
  }}
  onCopy={(selection) => {
    navigator.clipboard.writeText(selection.text)
  }}
  onClickHighlight={(selection, event) => {
    console.log('clicked highlight', selection.id)
  }}
/>

Custom popover

Pass your own component via PopoverChildren. It receives the selection plus action handlers:

import { PopoverChildrentype } from 'react-highlight-selector'

const MyPopover: PopoverChildrentype = ({
  selection,
  removeSelection,
  updateSelection,
  handleCopy,
  identifier,
}) => (
  <div className="my-popover">
    <span>{selection.text.length} chars</span>
    <button onClick={() => handleCopy?.(selection)}>Copy</button>
    <button
      onClick={() =>
        updateSelection(
          selection.id,
          { ...selection, className: 'bg-blue relative select-none' },
          identifier,
        )
      }
    >
      Make blue
    </button>
    <button onClick={() => removeSelection(selection, identifier)}>Delete</button>
  </div>
)

<Highlighter
  identifier="article"
  htmlString={articleHtml}
  PopoverChildren={MyPopover}
/>

Disable the popover or multi-color palette

<Highlighter
  identifier="notes"
  htmlString={notesHtml}
  disablePopover                 // no popover at all
/>
<Highlighter
  identifier="notes-2"
  htmlString={notesHtml}
  disableMultiColorHighlight     // popover shows, but without color swatches
/>

Saving and re-rendering selections

Selections are rehydrated from the input HTML itself: any <span id="selection-..." data-meta="..."> embedded in htmlString is converted back into an active highlight on mount. To persist highlights, serialize the container's HTML (including the highlight spans and their data-meta attributes) and feed it back later:

// Save: read the rendered container after highlighting
const container = document.getElementById('highlighter-rootdoc-1')
const savedHtml = container?.innerHTML ?? ''

// Restore later: pass the saved HTML right back in
<Highlighter identifier="doc-1" htmlString={savedHtml} />

You can also store the structured selections via the useSelections hook (below) — each SelectionType.meta is an xpath-range serialized range.

Reading all selections with useSelections

Must be used inside SelectionProvider. Selections are keyed by each highlighter's identifier:

import { useSelections } from 'react-highlight-selector'

function SelectionsPanel() {
  const { selections, addSelection, updateSelection, removeSelection } = useSelections()

  return (
    <ul>
      {(selections['doc-1'] ?? []).map((s) => (
        <li key={s.id}>
          {s.text}{' '}
          <button onClick={() => removeSelection(s, 'doc-1')}>remove</button>
        </li>
      ))}
    </ul>
  )
}

Multiple independent highlighters

Give each instance a distinct identifier; their selections are tracked separately in the same provider:

<SelectionProvider>
  <Highlighter identifier="chapter-1" htmlString={chapterOne} />
  <Highlighter identifier="chapter-2" htmlString={chapterTwo} />
</SelectionProvider>

API

<SelectionProvider>

Context provider that holds all highlight state. Required ancestor of Highlighter and useSelections.

| Prop | Type | Description | | ---------- | ----------------- | --------------- | | children | React.ReactNode | App/subtree |

<Highlighter>

| Prop | Type | Default | Description | | --------------------------- | --------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------ | | htmlString | string | — (required) | HTML content to render and make highlightable. Existing selection-* spans are rehydrated into highlights. | | identifier | string | — (required) | Unique key isolating this instance's selections in the provider. | | minSelectionLength | number | 10 | Minimum selected characters required to create a highlight. | | maxSelectionLength | number | — | Maximum selected characters allowed for a highlight. | | className | string | — | Class applied to the highlighter's root <div>. | | selectionWrapperClassName | string | 'bg-lightgreen relative select-none' | Class applied to newly created highlight spans. | | PopoverClassName | string | — | Class for the popover element (custom hover handling when set). | | PopoverChildren | PopoverChildrentype | built-in popover | Custom popover component rendered on hover. | | disablePopover | boolean | false | Hide the popover entirely. | | disableMultiColorHighlight| boolean | false | Hide the color swatches in the popover. | | onSelection | (selection: SelectionType) => void | — | Called when a new highlight is created. | | onCopy | (selection: SelectionType) => void | — | Called from the popover's copy action (enables the copy button). | | onClickHighlight | (selection: SelectionType, event: MouseEvent) => void | — | Called when an existing highlight is clicked. | | onClick | MouseEventHandler<HTMLDivElement> | — | Click handler for the root container. |

useSelections()

Returns highlight state and mutators. Throws if used outside SelectionProvider.

| Member | Type | Description | | ---------------- | ----------------------------------------------------------------------------- | ------------------------------- | | selections | Record<string, SelectionType[]> | All selections keyed by identifier. | | setSelections | Dispatch<SetStateAction<Record<string, SelectionType[]>>> | Replace state directly. | | addSelection | (selection: SelectionType, identifier: string) => Promise<void> | Add a selection (deduped by id). | | updateSelection| (id: string, updatedSelection: SelectionType, identifier: string) => Promise<void> | Replace a selection by id. | | removeSelection| (selection: SelectionType, identifier: string) => void | Remove a selection. |

Types

type SelectionType = {
  id: string                    // e.g. "selection-abc123"
  text: string                  // highlighted text
  meta: string                  // xpath-range serialized range (for rehydration)
  className?: string            // highlight span classes
  startContainerText?: string
  endContainerText?: string
  [key: string]: any            // open for your own fields
}

type PopoverChildrentype = React.FC<{
  selection: SelectionType
  identifier: string
  handleCopy?: (selection: SelectionType) => void
  removeSelection: (selection: SelectionType, identifier: string) => void
  updateSelection: (id: string, updatedSelection: SelectionType, identifier: string) => void
  disableMultiColorHighlight?: boolean
}>

type XpathType = {
  start: string
  end: string
  startOffset: number
  endOffset: number
}

Constants

| Export | Value | | --------------------------------- | -------------------------------------- | | defaultColor | '#15F5BA' | | defaultSelectionWrapperClassName| 'bg-lightgreen relative select-none' | | defaultMinSelectionLength | 10 |

Development commands

| Command | Description | | -------------------- | -------------------------------------------------------------- | | npm run build | Build both ESM (dist/esm) and CJS (dist/cjs) outputs. | | npm run build:esm | Build ESM output + type declarations with tsc. | | npm run build:cjs | Build CommonJS output to dist/cjs. | | npm run lint | Lint all js/ts/jsx/tsx files with ESLint. | | npm run prettier | Format src, tests, and example/src with Prettier. | | npm test | Test script placeholder (no tests configured yet). | | npm run prepare | Runs automatically on install; builds the package. | | npm run prepublishOnly | Pre-publish gate: test + prettier + lint. |

Typical contributor flow:

npm install        # installs deps and builds via `prepare`
npm run lint       # check code style
npm run build      # produce dist/

License

MIT © Dhyan T D