Skip to content

API — @responsivejs/design

The validation oracle. Peers (both optional): @playwright/test (only the driver needs it), axe-core (only a11y needs it). The /browser subpath is driver-free by construction.

r$(page) — the Playwright validator

typescript
const r = r$(page);                       // ResponsiveValidator
await r.sweep({ url, selectors, widths | from/to/step, height?, scroll?, scrollSteps? });
MemberMeaning
r.at(width)WidthQuery at a measured width (rect, style, elements…).
r.curve(sel, prop) / r.rectCurve(sel, prop)Property curve across widths.
r.assertThe Asserter (chainable constraints, below).
r.score(parentSelector?)ScoreResult — 17-metric aesthetics, whole page or subtree.
r.report()Constraint Report.
r.analyze(opts?)The full oracle → UnifiedReport (reuses the sweep).
r.log('console' | 'json' | 'compact')Print the report.
r.resweep({ widths?, selectors? })Incremental re-measure, merged.
r.validateDesignSystem(ds, selectors?)Apply a DS profile's constraints.
r.measureInteraction(sel)Normal/hover/focus snapshots.
r.widths / r.rawMeasured widths / the SnapshotStore.
ResponsiveValidator.live(page, { selectors })A LiveValidator (below).

The Asserter — 27 chainable constraints

Every check runs at all measured widths; violations carry structured fix suggestions where honest. new Asserter(store) works on any store (no Playwright).

noOverflow() · contains(parent, child) · sameHeight(a, b, tol?) · sameLine(a, b) · minSize(sel, { width?, height? }) · gapUniform(sel, threshold?) · monotonic(sel, prop, dir?) · continuous(sel, prop, maxJump) · proportion(a, b, { min, max }) · childrenContained(sel, tol?) · childrenEqualWidth(sel, tol?) · noZeroHeight(sel) · touchTarget(sel, min?) (default 24 = WCAG 2.5.8 AA floor; platform guidance 44–48; interactive = DOM semantics — native controls/roles/tabindex — or cursor:pointer; inline targets in prose exempt; unrendered skipped) · textReadable(sel) · contrastRatio(sel, 'AA' | 'AAA') · borderRadiusValid(sel) · zStackOrder(selectors) · typographyScale(sel) · spacingTokens(sel, tokens) · aspectRatio(sel, ratio, tol?) · focusVisible(sel) · noHiddenOverflow(sel) · alignedToGrid(sel, gridSize) · breakpointSafe(breakpoints) · interactiveSpacing(sel, minGap?) · visible(sel) · hidden(sel) — then report() / reset().

analyze() — the unified oracle

typescript
analyze(opts: AnalyzeOptions): Promise<UnifiedReport>
analyzeStore(store, opts?): UnifiedReport          // sync, pure — also on /browser

AnalyzeOptions: source (a MeasurementSource) and/or store (pre-measured); sweep params (url, selectors, widths/from/to/step, height, scroll); constraints (config object or (assert) => void escape hatch); designSystem; score ({ subtree? } | false); a11y (A11yOptions | false).

Default constraint set (low false-positive): noOverflow + contrastRatio AA + touchTarget on every analyzed selector. ConstraintsConfig toggles each and adds textReadable, focusVisible, or anything via custom(assert).

UnifiedReport extends Report with: clean (zero violations of any kind — pass fails only on error severity), scores, fixes (FixSuggestion[] — only kind: 'exact' entries, deduped by selector+property across widths: the apply-verbatim agent surface), widths, url, sources ({ measurement, a11y: 'axe' | 'skipped' | 'unavailable' }), summary (errors/warnings/info, byRule, byWidth), durationMs.

a11y degradation: omitted + axe installed → runs; omitted + missing → 'unavailable' (silent); explicitly configured + missing → throws; no evaluate seam or a11y: false'skipped'. Axe rules are namespaced axe:<id>; impact maps critical/serious → error, moderate → warning, minor → info. color-contrast is always disabled (delegated to the deterministic constraint). A11yOptions: wcagTags, disableRules, widths: number[] | 'all' (default [min, max]), include, exclude.

mergeReports(base, ...extra) folds additional plain Reports (e.g. a theme token gate) into one unified report.

MeasurementSource — drivers

typescript
interface MeasurementSource {
    kind: string;
    open?(url): Promise<void>;
    setViewport(width, height): Promise<void>;
    measure(selectors): Promise<ViewportSnapshot>;
    evaluate?<T>(expression: string): Promise<T>;   // string-only (CDP-compatible)
    close?(): Promise<void>;
}
  • PlaywrightSource(page, { settleMs? }) — the CI driver.
  • CdpSource(client, { height?, settleMs?, loadTimeoutMs? }) — takes any structural { send(method, params) } (chrome-remote-interface, Playwright CDPSession, agent-browser). Measures by injecting the collector via Runtime.evaluate (returnByValue + awaitPromise).
  • EvalSource(evalFn, { setViewport?, open?, settleMs?, widthTolerance? }) — the lowest-friction adapter: wraps a bare (expression: string) => Promise<unknown> primitive (agent-browser, extensions, bookmarklet hosts). setViewport/open are optional callbacks; without a viewport setter the source verifies the live width instead of lying about it — currentWidth() gives you the natural sweep width. String results from text transports are JSON-parsed in measure().
  • sweepSource(source, opts) / resweepSource(source, store, opts) — the driver-neutral sweep loop (scroll support requires the evaluate seam). SourceSweepOptions makes url optional: omit it for pre-navigated/attached sources.

/browser — zero-driver

collectViewport(selectors, { root?, width?, height? }) · collectStore(...) · scoreDOM(selectors) · analyzeDOM(selectors?, opts?) (one-call oracle over the live DOM at the current viewport — defaults to LANDMARK_SELECTORS) · analyzeStore · collectPage / buildCollectExpression(args) (the self-contained in-page collector and its injectable expression) · fromWire/toWire · storeToJSON/storeFromJSON (JSON transport of stores — Maps don't survive serialization).

The overlay: mountOverlay({ selectors?, touchMin? }) appends an <rjs-overlay> badge (shadow DOM, style-isolated) that shows live E/W counts, expands to the grouped findings, outlines the offending element on hover, and re-measures on resize. defineOverlay() registers the element without mounting. Both are lazy — safe to import in Node/SSR.

The IIFE bundle: @responsivejs/design/browser-global is this whole subpath bundled as a single ~52 kB script exposing window.rjs — droppable into any page via <script>, page.addScriptTag, or the rjs snippet bookmarklet. No module system needed.

LiveValidator — realtime observers

attach(page, selectors) · snapshot(): SnapshotStore · resizeTo(width) · scoreAt(width?) · check(): Report · detach(). For continuous scoring while a page changes (theme builders, devtools). It runs the same in-page collector as every other path, so its measurements are identical — DOM-semantic interactivity, effective backgrounds, overflow containment and the provenance manifest all included.

Reporters

formatConsole · formatJSON (serializes score maps correctly) · formatCompact (adds E/W/I counts for unified reports) · formatSARIF(unified, { toolVersion? }) — SARIF 2.1.0 for code-scanning CI · formatContractConsole / formatContractCompact / formatContractSARIF (contract rule ids become SARIF rule ids; the authored description rides as each rule's shortDescription — see contract) · toSerializable · renderAuditHTML(pages: PageAudit[], { title? }) — one self-contained HTML document per audit run: grouped violations, fixes, embedded per-width screenshots with violation rectangles overlaid from the measured rects; 2+ pages lead with a comparison table.

Design systems

Bundled profiles (@responsivejs/design/design-systems/*.json): apple-hig, fluent-ui-2, material-design-3. applyDesignSystem(asserter, ds, selectors?) applies a profile's constraints; designSystemRules(ds, selectors?) returns the same checks as contract rules (the two are parity-tested).

Also exported

Pieces of the pipeline, exported because an agent loop or a custom harness may need them without going through analyze():

contractFromPage(store, { name? })the contract ANY page can be held to, from what the sweep found — plus construct-derived rules when the page runs r$
contractFromManifest(manifest, { name? })only the construct-derived half
INIT_SELECTORSthe candidate selectors rjs init sweeps to decide which rules are worth writing
resolveWidths(opts)the width list a sweep will actually use (contract → options → defaults)
applicableFixes(report)the exact fixes only, deduplicated — what a fixer may apply unattended
attachOwnership(report, manifest)adds owner / owners / via to violations from the provenance manifest
runAxe(source, opts) / normalizeAxeResults(results)the a11y pass on its own, and its mapping to r$ severities
compileRule(rule)one contract rule → the asserter call it becomes
calibrate(store)derives sensible constraint thresholds from a page you consider correct