
Security News
pnpm 11.5 Adds Support for Recognizing npm Staged Publishes
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.
@uindow/css
Advanced tools
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.
npm
npm install @uindow/css
const { findOne, findAll } = require("@uindow/css");
Browser (standalone)
<script src="https://uindow.github.io/css/selector.js"></script>
<script>
const { findOne, findAll } = Uindow_CSS;
</script>
const { findOne, findAll } = require("@uindow/css");
// The single best selector
const { selector, penalty } = findOne(document.querySelector(".hero"));
// → '[data-id="42"] .hero'
// Every viable selector, ranked from best to most specific
const results = findAll(document.querySelector(".hero"), { maxResults: 5 });
// → [
// { selector: '[data-id="42"] .hero', penalty: 1.3 },
// { selector: '.hero', penalty: 1.3 },
// { selector: 'section [data-id="42"] .hero', penalty: 2.6 },
// { selector: 'main .hero', penalty: 2.75 },
// { selector: 'main .hero:nth-of-type(1)', penalty: 4.4 },
// ]
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.
@medv/finder| Feature | @uindow/css | @medv/finder |
|---|---|---|
| Custom root element | ✅ Any HTMLElement, Document, or ShadowRoot | ✅ Supported |
| ID filter | ✅ idFilter filter | ✅ idName filter |
| Tag filter | ✅ tagFilter filter | ✅ tagName filter |
| Class filter | ✅ Excludes is-*, has-*, js-*, css-* by default | ⚠️ Less opinionated defaults |
| Attribute filter | ✅ Excludes style, width, height, URLs, values ≥ 32 chars by default | ⚠️ Less opinionated defaults |
| Search timeout | ✅ timeout with graceful fallback | ⚠️ May quit before an exhaustive search |
| Returns multiple selectors | ✅ Up to maxResults, ranked by penalty | ❌ Single selector only |
| Per-type penalty tuning | ✅ idPenalty, tagPenalty, attrPenalty, attrMatchPenalty, classPenalty, nthOfTypePenalty, nthChildPenalty, lengthPenaltyThreshold | ❌ Not configurable |
| Candidate/path caps | ✅ maxCandidatesPerLevel, maxPathsPerLevel, maxPathsTotal | ❌ Not supported |
| Prefix/suffix attribute matching | ✅ [attr^="start"], [attr$="end"] | ❌ Not supported |
| Human-readable attribute selectors | ✅ Always emits [attr="123"] | ❌ Uses CSS.escape() |
| Fuzziness | ✅ Trade exclusivity for shorter selectors (0% to 100%) | ❌ Not supported |
| Effort | ✅ Trade processing time for shorter selectors (0% to 100%) | ❌ Not supported |
| Compound selectors | ✅ Attempts to merge tag, classes, and attributes at each level: input.check[type="checkbox"][value="2"] | ❌ Simple selectors only |
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 }) => {
console.log(`${selector} (penalty: ${penalty})`);
});
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 */
[data-id="42"]
/* Prefix match - used when it uniquely identifies the element */
[data-id^="prod-"]
/* Suffix match */
[data-id$="-active"]
Tune how eagerly these appear with attrMatchPenalty (default 1.2).
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 */
[data-id="123"] .nav-item
/* ❌ Other tools */
[data-id="\31 23"] .nav-item
Unstable and noisy parts of the DOM are excluded before the search begins:
style, width, height, URL values, and anything over 32 characters are ignored by default.is-*, has-*, js-*, and css-* are ignored by default. These are state and behaviour hooks - they change at runtime and make brittle selectors.idFilter to exclude auto-generated or dynamic ones.Every filter is a plain function, so your own rules are one line of code.
Each selector type has an independent penalty weight. Lower means it shows up more often in results; higher means it only appears when nothing better is available.
| Option | Default | Controls |
|---|---|---|
idPenalty | 1 | #id selectors |
tagPenalty | 1.1 | div, span, … |
attrPenalty | 1.25 | [name], [value="1"] |
attrMatchPenalty | 1.2 | [name^="x"], [value$="5"] |
classPenalty | 1.3 | .button, .nav-item, … |
nthOfTypePenalty | 3 | div:nth-of-type(2) |
nthChildPenalty | 6 | :nth-child(3) |
lengthPenaltyThreshold | 32 | Extra penalty for selectors longer than N chars |
For large or deeply nested DOMs, hard caps keep the search bounded:
const { findAll } = require("@uindow/css");
findAll(el, {
maxCandidatesPerLevel: 2500, // Candidates evaluated per ancestor level
maxPathsPerLevel: 50, // Unique paths kept per level
maxPathsTotal: 1000, // Unique paths across the entire search
timeout: 1500, // Return best found so far after N ms
});
By default, @uindow/css only returns selectors that match the target element exclusively. Set fuzziness > 0 to allow selectors that match the target first while potentially matching other elements too - trading strict uniqueness for shorter, cleaner output.
findAll(el, { fuzziness: 0 }); // Strict - only exclusive selectors (default)
findAll(el, { fuzziness: 5 }); // Up to 5% non-exclusive matches allowed
findAll(el, { fuzziness: 20 }); // Relaxed - prioritise brevity
findAll(el, { fuzziness: 100 }); // Fuzzy - only non-exclusive (first-match) selectors
Controls how much effort is spent optimizing partial results before returning the final list. This setting trades speed for result quality: higher values spend more time exploring candidates before producing the final result set.
findAll(el, { effort: 0 }); // Low effort - quickly returns the first `maxResults`
findAll(el, { effort: 50 }); // Medium effort - evaluates 25 × `maxResults` candidates before returning the final results
findAll(el, { effort: 100 }); // Maximum effort - evaluates 50 × `maxResults` candidates before returning the final results
Most CSS selector generators pick a single token per ancestor level - a class, or an attribute, or a tag - and move on. This produces selectors that are either fragile (too generic) or bloated (too many levels deep).
@uindow/css builds compound selectors at each level by default, combining the tag, relevant classes, and attributes into a single, precise token:
/* ✅ @uindow/css - compound, precise, readable */
input.check[type="checkbox"][value="2"]
/* ❌ Other tools - simple, one token at a time */
input
.check
[type="checkbox"]
The result is selectors that are shorter, more stable, and immediately understandable at a glance.
findOne(element, options?)Returns the single best selector for element, or throws if none can be generated.
const { findOne } = require("@uindow/css");
const { selector, penalty } = findOne(document.getElementById("main"));
// → { selector: '#main', penalty: 1 }
findAll(element, options?)Returns up to maxResults selectors sorted by ascending penalty, or throws if none can be generated.
const { findAll } = require("@uindow/css");
const results = findAll(document.getElementById("main"), {
maxResults: 10,
});
All configuration items are optional. Any omitted option falls back to its default.
const { findAll } = require("@uindow/css");
findAll(el, {
// Scope - nothing above this element appears in any selector
root: document.querySelector("#app"), // Default: document.documentElement
// Filters - return false to exclude a candidate from the search
idFilter: (name) => !name.startsWith("auto-"),
tagFilter: (name) => name !== "div",
classFilter: (name) => !name.startsWith("is-"),
attrFilter: (name, value) => name.startsWith("data-") && value.length < 32,
// Penalties - lower value → type appears more often in results
idPenalty: 1,
tagPenalty: 1.1,
attrPenalty: 1.25,
attrMatchPenalty: 1.2,
classPenalty: 1.3,
nthOfTypePenalty: 3,
nthChildPenalty: 6,
lengthPenaltyThreshold: 32,
// Performance caps
maxCandidatesPerLevel: 2500,
maxPathsPerLevel: 50,
maxPathsTotal: 1000,
maxResults: 25,
timeout: 1500,
// Percentage of shorter, first-match selectors
fuzziness: 0,
// Effort spent on optimizing candidates
effort: 50
});
MIT © 2026 Uindow™
FAQs
CSS Selector Generator
The npm package @uindow/css receives a total of 384 weekly downloads. As such, @uindow/css popularity was classified as not popular.
We found that @uindow/css demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.