Show HN: A smarter CSS selector generator

bigrocketapps1 pts0 comments

@uindow/css - npm

npm

Search<br>Sign UpSign In

@uindow/css

1.0.0 • Public • Published 4 hours ago<br>Readme<br>Code Beta<br>0 Dependencies<br>0 Dependents<br>2 Versions<br>@uindow/css - The Smarter CSS Selector Generator

Generate one - or many - unique CSS selectors for any DOM element. Clean, human-readable output. A configurable penalty model that ranks results from most semantic to most specific. Built for performance, designed to stay out of your way.

Works everywhere JavaScript runs: Node.js, browsers, and any environment with a DOM.

Installation

npm

npm install @uindow/css

const { findOne, findAll } = require("@uindow/css");

Browser (standalone)

script src="https://uindow.github.io/css/selector.js">script><br>script><br>const { findOne, findAll } = Uindow_CSS;<br>script>

Quick start

const { findOne, findAll } = require("@uindow/css");

// The single best selector<br>const { selector, penalty } = findOne(document.querySelector(".hero"));<br>// → '[data-id="42"] .hero'

// Every viable selector, ranked from best to most specific<br>const results = findAll(document.querySelector(".hero"), { maxResults: 5 });<br>// → [<br>// { selector: '[data-id="42"] .hero', penalty: 1.3 },<br>// { selector: '.hero', penalty: 1.3 },<br>// { selector: 'section [data-id="42"] .hero', penalty: 2.6 },<br>// { selector: 'main .hero', penalty: 2.75 },<br>// { selector: 'main .hero:nth-of-type(1)', penalty: 4.4 },<br>// ]

Why @uindow/css?

Most CSS selector generators treat the problem as a lookup: walk up the DOM, find something unique, done. One selector, take it or leave it.

@uindow/css treats it as a search problem . It explores the space of possible selectors across every ancestor level, scores each candidate against a configurable penalty model, prunes aggressively to stay fast, and surfaces a ranked list of results - so you always get the shortest, most readable, most stable selector first, with fallbacks already computed.

Comparison with @medv/finder

Feature<br>@uindow/css<br>@medv/finder

Custom root element<br>✅ Any HTMLElement, Document, or ShadowRoot

✅ Supported

ID filter<br>✅ idFilter filter<br>✅ idName filter

Tag filter<br>✅ tagFilter filter<br>✅ tagName filter

Class filter<br>✅ Excludes is-*, has-*, js-*, css-* by default<br>⚠️ Less opinionated defaults

Attribute filter<br>✅ Excludes style, width, height, URLs, values ≥ 32 chars by default<br>⚠️ Less opinionated defaults

Search timeout<br>✅ timeout with graceful fallback<br>⚠️ May quit before an exhaustive search

Returns multiple selectors<br>✅ Up to maxResults, ranked by penalty<br>❌ Single selector only

Per-type penalty tuning<br>✅ idPenalty, tagPenalty, attrPenalty, attrMatchPenalty, classPenalty, nthOfTypePenalty, nthChildPenalty, lengthPenaltyThreshold

❌ Not configurable

Candidate/path caps<br>✅ maxCandidatesPerLevel, maxPathsPerLevel, maxPathsTotal

❌ Not supported

Prefix/suffix attribute matching<br>✅ [attr^="start"], [attr$="end"]

❌ Not supported

Human-readable attribute selectors<br>✅ Always emits [attr="123"]

❌ Uses CSS.escape()

Fuzziness<br>✅ Trade exclusivity for shorter selectors (0% to 100%)<br>❌ Not supported

Compound selectors<br>✅ Attempts to merge tag, classes, and attributes at each level: input.check[type="checkbox"][value="2"]

❌ Simple selectors only

Features

Multiple selectors, ranked by quality

findAll() returns up to maxResults selectors sorted by ascending penalty - from the most semantic to the most specific. Pick the best one programmatically, or hand the whole list to your users.

const { findAll } = require("@uindow/css");

const results = findAll(el, { maxResults: 10 });

results.forEach(({ selector, penalty }) => {<br>console.log(`${selector} (penalty: ${penalty})`);<br>});

Prefix and suffix attribute matching

When an exact attribute match would be brittle or unavailable, @uindow/css can generate selectors using the CSS ^= (prefix) and $= (suffix) operators. They carry their own separate penalty so they only surface when genuinely useful.

/* Exact match - always preferred */<br>[data-id="42"]

/* Prefix match - used when it uniquely identifies the element */<br>[data-id^="prod-"]

/* Suffix match */<br>[data-id$="-active"]

Tune how eagerly these appear with attrMatchPenalty (default 1.2).

Clean, human-readable output

Every selector is emitted in the plain [attr="value"] format. No CSS-escaped sequences, no surprises when you read, copy, or paste the result.

/* ✅ @uindow/css */<br>[data-id="123"] .nav-item

/* ❌ Other tools */<br>[data-id="\31 23"] .nav-item

Sensible default filters

Unstable and noisy parts of the DOM are excluded before the search begins:

Attributes - style, width, height, URL values, and anything over 32 characters are ignored by default.

Classes - is-*, has-*, js-*, and css-* are ignored by default. These are state and behaviour hooks - they change at runtime and make brittle selectors.

IDs - all IDs are allowed by default. Provide your own idFilter to exclude auto-generated or dynamic ones.

Every filter is a plain function, so your own rules are one line of code.

Fine-grained penalty model

Each...

selector penalty uindow selectors filter hero

Related Articles