Getting started
ResponsiveJS (r$) is one model — value = f(width) — with two halves: author responsive behavior CSS can't express, verify the rendered result with measurements.
Start with the second half. It costs one command, changes nothing in your codebase, and tells you something true about a site you already have:
npx @responsivejs/cli analyze https://your-site.com -w 320,375,768,1024,1280,1920Every width judged: what overflows, which targets miss the WCAG 24px floor, where contrast fails against the background actually painted, whether the page scrolls sideways on a phone. Exit 0 pass, 1 violations — so it is a CI gate the moment you want one. Nothing installed, nothing imported, no decision made.
Liked what it found? Pin it as a contract — still without writing r$ code. Only then is the authoring half worth its install: it is for the cases CSS genuinely cannot express, not for replacing CSS you already have.
- Adopting r$ in a site you already have — the five-step path, each step useful on its own
- The tutorial — the other direction: build a page from nothing, ~30 minutes
- Why r$ — "I can write
clamp()myself", "we have visual regression testing", and when not to use it - Troubleshooting — by symptom
What it replaces
| The hack you write today | The r$ construct | What you gain |
|---|---|---|
A @media ladder for every size | r$.tokens({ '--space-m': r$.fluid(16, 24) }) | Smooth scaling, static clamp(), zero JS |
| The burger breakpoint that rots | r$.geometry('.nav', { wrapped: r$.whenWraps }) | Adapts by measurement — add a link, still correct |
| IntersectionObserver sticky-sentinel | r$.whenStuck() | One line, no sentinel DOM |
| Resize listeners + manual measuring | r$.sync, r$.ratio, r$.fromElement | Cross-element relations, cleanup included |
'mobile' strings that typo at runtime | r$.breakpoints({...} as const) | Names the compiler checks |
| Squinting at three screen sizes | rjs analyze <url> | Measured verdict at every width, exit-code gated |
| Screenshot diffing for regressions | rjs record / verify contracts | The layout's rules as reviewable JSON |
Pick your entry:
| I want to… | Install | Start here |
|---|---|---|
| Audit a URL right now, zero setup | nothing — npx @responsivejs/cli | § Audit |
| Author responsive behavior (fluid, geometry, tokens) | npm i @responsivejs/runtime | § Authoring |
| Validate/score a page in CI | npm i -D @responsivejs/design @playwright/test | § Validation |
| Score a live DOM without any driver | npm i @responsivejs/design | § Zero-driver |
| Pin a layout down as a verifiable contract | npm i -D @responsivejs/contract | § Contracts |
| Use it in React / Vue / Angular | npm i @responsivejs/react · /vue · /angular | § Adapters |
| Just the math (curves, geometry, WCAG, aesthetics) | npm i @responsivejs/core | API: core |
| Drive r$ as an AI agent | — | agents docs |
No bundler? <script src="…/@responsivejs/runtime/dist/global.js"></script> gives you the same r$ on window (~15.5 kB gzip) — CMS pages, plain HTML, live demos.
All packages are ESM-only, zero runtime dependencies (Playwright and axe-core are optional peers of design), Node ≥ 20.19, MPL-2.0.
Audit (CLI)
npx @responsivejs/cli analyze https://example.com -w 320,768,1280
# constraints + a11y · exit 0 pass / 1 violations · -f json|sarif · --score adds the heuristic
npx @responsivejs/cli audit https://example.com --vs https://competitor.com
# → one self-contained HTML report: screenshots with violation overlays, side-by-sideDriver-pluggable (Playwright, or agent-browser for any live URL with nothing installed). verify/record run the contract flow.
→ CLI reference · the design guide
Authoring
One import, the whole surface behind your editor's autocomplete:
import { r$ } from '@responsivejs/runtime';
const bp = r$.breakpoints({ mobile: 320, tablet: 768, desktop: 1024 } as const);
r$.tokens({ '--space-m': r$.fluid(16, 24), '--font-hero': r$.fluid(28, 64) }); // clamp() on :root
r$.geometry('.site-nav', { wrapped: r$.whenWraps }); // CSS: .site-nav[data-wrapped] { … }
r$('.cards', { gridTemplateColumns: bp.below('tablet', '1fr', 'repeat(3, 1fr)') });r$ is CSS-first: everything expressible as clamp()/@media becomes one injected stylesheet; JS drives only what CSS cannot — non-linear curves, geometry state (wrap, overflow, sticky, truncation), cross-element dependencies (fromElement, sync, ratio). Add { container: true, from, to } to bind a value to the nearest container instead of the viewport — from/to are the container's own range, and are required.
→ the runtime guide (purposes, gradual examples, the mental model) · case studies · API: runtime · live example
Adapters
The constructs are framework-agnostic; the adapters own the lifecycle.
// React
const ref = useRef<HTMLDivElement>(null);
useResponsive(ref, { padding: r$.fluid(12, 24) }); // applied on mount, disposed on unmount
const isDesktop = useBreakpoint('desktop');<!-- Vue -->
<script setup>
const card = ref(null);
useResponsive(card, { padding: r$.fluid(12, 24) });
</script>
<template><div ref="card" v-responsive="{ gap: r$.fluid(8, 16) }" /></template>Changing a declaration calls update() on the live handle instead of recreating it; React's StrictMode double-invocation is handled. Angular ships decorator-free helpers (injectResponsive, injectViewportWidth, …) that need no compilation step. → adapters reference
Validation
import { test, expect } from '@playwright/test';
import { r$ } from '@responsivejs/design';
test('layout holds at all widths', async ({ page }) => {
const r = r$(page);
await r.sweep({ url: 'http://localhost:3000', widths: [320, 768, 1280], selectors: ['h1', '.btn'] });
r.assert.noOverflow().minSize('.btn', { height: 44 }).monotonic('h1', 'fontSize', 'up');
expect(r.report().pass).toBe(true);
});Or run the whole oracle in one call — constraints + aesthetic score + a11y (axe):
const report = await r$(page).sweep({ ... }).then((r) => r.analyze());
// UnifiedReport: { pass, violations, fixes, scores, summary, … }→ the design guide (measure → model → judge) · API: design · guide: CI
Zero-driver
The browser subpath runs anywhere a DOM exists — no Playwright:
import { scoreDOM, analyzeStore, collectStore } from '@responsivejs/design/browser';
const { average, suggestions } = scoreDOM(['main', '.card']); // 17-metric aesthetic score
const report = analyzeStore(collectStore(['main', '.card'])); // full constraint reportInject it into any page via a driver's eval (CDP, agent-browser) — see the agents guide.
Contracts
import { contract } from '@responsivejs/contract';
import { verifyContract } from '@responsivejs/design';
const home = contract('home')
.select('sidebar', '.app-sidebar')
.assert('noOverflow', undefined, { id: 'no-bleed' })
.below(768).assert('hidden', { selector: '$sidebar' }, { id: 'sidebar-mobile' })
.from(768).assert('visible', { selector: '$sidebar' }, { id: 'sidebar-desktop' })
.build();
const report = await verifyContract(home, page); // sweep derived from the contractContracts serialize to JSON (published schema), travel with the repo, and double as the machine-readable spec agents enforce.
Development setup (this repo)
git clone https://github.com/AleSaiani/ResponsiveJS.git && cd ResponsiveJS
pnpm install
pnpm test # unit tests, plain Node
pnpm typecheck # no build needed (source-resolved)
pnpm build # tsc, topological
pnpm test:e2e # needs: pnpm --filter @responsivejs/design exec playwright install chromium