finder

Generates the shortest, unique, and robust CSS selector for any DOM element in just 1.5kb.

Library
npm
v4.0.2
1,488stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
44/100Fair
Development Activity0
Maintenance20
Community56
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
62/100Good
Architecture65
Code Quality72
Innovation75
Learning Curve35

@medv/finder is a zero-dependency CSS selector generator for the browser. Given any DOM element, it searches upward through ancestors and combines IDs, classes, attributes, and tag names to build the shortest selector string that uniquely matches that single node on the page, falling back to nth-of-type paths when nothing shorter is unique.

It ships as a single ~1.5kb (minified & gzipped) ESM module with a fully configurable matching strategy — you can restrict which class names, attribute names/values, and IDs are considered safe to use in a selector, cap the search with a timeout, and tune how aggressively the resulting selector gets optimized for brevity. It’s the selector-generation engine behind several session-replay, click-tracking, and browser-automation recorder tools.

What You Get

  • Shortest unique CSS selector generation for any element, with automatic fallback to nth-of-type paths when no shorter selector is unique
  • Fully pluggable matching rules for id, class, tag, and attribute names via idName/className/tagName/attr predicate functions
  • Configurable root scope, search timeout, and path-check limits so selector generation never blocks the main thread for long
  • Zero runtime dependencies and a ~1.5kb minified+gzipped bundle size
  • Shadow DOM aware root resolution for selectors generated inside web components

Common Use Cases

  • Click and event tracking - product analytics tools generate a stable CSS selector for the exact element a user clicked so events can be aggregated or replayed later
  • Session replay recorders - browser extensions and SDKs that record user sessions use finder to describe which element on the page an action targeted
  • Browser automation test generators - codegen-style tools that record a user’s interactions and emit selector-based test scripts
  • Heatmap and click-map tooling - tools that aggregate recorded interactions need a reliable selector to bucket events by element rather than raw coordinates

Under The Hood

Architecture The module is a flat, functional pipeline in a single file (finder.ts, ~385 lines) with no class hierarchy or DI: tie() scores an ancestor’s id/class/attribute/tag-name/nth-child candidates with a penalty value, search() walks up the tree building per-level candidate sets and lazily yields sorted path combinations via a combinations() generator, unique() validates a candidate against the live DOM with querySelectorAll, and a separate optimize() generator then tries removing intermediate path segments from a working selector while it stays unique and still resolves to the original element. A fallback() path handles the timeout case with an nth-of-type-only selector. Because everything lives in one file with no internal layering, a change to the Knot shape or penalty scoring ripples directly through every function that touches it.

Tech Stack Written in TypeScript (5.7.2), compiled with plain tsc (no bundler), published as ESM-only (type: module) with zero runtime dependencies — devDependencies are limited to css.escape (a CSS.escape polyfill used in the test environment), jsdom and vitest for testing, prettier for formatting, and typescript for the build. It’s dual-published to npm (compiled finder.js/finder.d.ts) and JSR (jsr.json points straight at the TypeScript source). Two GitHub Actions workflows run a Node 20/22 test matrix and a prettier --check formatting gate — there is no linter and no bundler tooling.

Code Quality Tests in tests/finder.test.js use vitest + jsdom and snapshot-test the selectors generated across several real-world captured HTML pages (github.com, stripe.com, deployer.org, tailwindcss.com, a shadow-DOM fixture), asserting every generated selector both selects exactly one node and resolves back to the originating element — a strong end-to-end correctness check, though there are no isolated unit tests of internal helpers like tie(), combinations(), or optimize(). The code is fully typed (an explicit Options type, typed Knot records, no observed any), and errors from invalid input or an unresolvable selector are raised explicitly with throw new Error(...) rather than swallowed. Style is enforced only via prettier in CI; there is no ESLint configuration.

API Design The entire public surface is one function, finder(element, options?), with sensible defaults for every option (root, timeoutMs, seedMinLength, optimizedMinLength, and the four naming predicates) so the zero-config call finder(event.target) works immediately. Power users can override just the idName/className/tagName/attr predicates without needing to understand the internal Knot/penalty model, and the library exports its default predicate functions individually so callers can wrap and extend rather than fully replace them — a pattern the README documents with copy-pasteable examples. Documentation is limited to the README (no separate docs site or generated API reference), but its option table covers every config field and default.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search