English · 简体中文
Documentation · Playground · Configuration · llms.txt
You write px the way the design file says. The compiler converts it. But converting means nothing until you know which design file that px was drawn on — and that is the whole model.
Your pages are drawn on a 750 file. The mobile component library you installed was drawn on 375. The desktop one has no design file at all — it was drawn in real pixels. Push all three through one conversion formula and you are picking which of three answers to get wrong.
An ignore list does not rescue this. It offers exactly two outcomes: the page scales while the components stay put, or the components get stretched against a canvas that was never theirs.
Giving each design file its own canvas is the answer. And it is the default — there is nothing to configure.
npm i -D postcss postcss-adaptive-matrixFor build scripts and editor tooling, the package also exposes a direct compilation API:
import { compileAdaptiveCss } from 'postcss-adaptive-matrix'
const { css, warnings } = await compileAdaptiveCss('.card { padding: 24px }')Only the CSS input is required. Use createAdaptiveCompiler() to reuse configuration across files, or supply optional PostCSS source-map settings and browser targets. See programmatic compilation for result fields and error handling.
Start with no options in postcss.config.mjs:
import adaptiveMatrix from 'postcss-adaptive-matrix'
export default {
plugins: [adaptiveMatrix()],
}This uses the built-in profiles and library routes without injecting global root styles. No configuration field is required to get started. Customise only what differs from your design or layout; for example, the app/desktop preset below uses a #app root foundation:
import adaptiveMatrix, { appPcPreset } from 'postcss-adaptive-matrix'
export default {
plugins: [
adaptiveMatrix(
appPcPreset({
appDesignWidth: 375,
pcDesignWidth: 1440,
rootSelector: '#app',
}),
),
],
}Write CSS as you always have:
.page {
padding: 16px;
font-size: 16px;
}
@adaptive pc {
.page {
padding: 48px 64px;
}
}Output:
.page {
padding: clamp(13.65333px, 4.26667vw, 20.48px);
font-size: clamp(0.94867rem, calc(0.65rem + 1.49333vw), 1.098rem);
}
@media (min-width: 768px) {
.page {
padding: clamp(34.13333px, 3.33333vw, 64px) clamp(45.51111px, 4.44444vw, 85.33333px);
}
}At this point component-library adaptation, safe-area variables, the centred root column and the position: fixed correction are all already working. No further configuration.
| Multiple canvases | App, desktop, tablet, in-car — each with its own design width, breakpoint, fluid range and unit |
| Bounded fluid sizing | clamp() by default: no runaway growth on a wide screen, no collapse on a narrow one |
| Root-relative text | A rem + vw hybrid retains a root-relative component; real zoom acceptance is still required |
| Component libraries built in | 12 adaptation entries across 11 libraries: mobile kits use their own canvases; desktop kits keep authored pixels by default |
| Theme tokens | Library custom properties on :root are recognised by name; font sizes take the text formula automatically |
| Fixed-position correction | position: fixed stops escaping to the viewport edge inside a centred column |
| Container queries | unit: 'cqi' with @container: sizes follow an ancestor rather than the window |
| Atomic CSS | Tailwind and UnoCSS, both major versions, including the theme tokens their utilities read |
| Optional runtime | A VisualViewport observer for WebViews, on-screen keyboards and dynamic viewports |
| CLI preview + JSON report | Inspect converted declarations interactively or feed versioned diagnostics to CI — no build, no browser |
| Breakpoint seam check | Finds every place where widening the window makes something smaller — where two design files disagree |
| Browser support audit | Checks the compiled output against the oldest browsers you intend to support |
| Engineering | Complete TypeScript types, ESM + CJS, and a language-agnostic conformance suite |
Plain vw has no size limits. The built-in profiles include lower and upper bounds: inside their fluid range sizes track the viewport, outside it they stop. These are preset choices, not required fields for custom profiles.
fluid is optional, and so are both minWidth and maxWidth: omit fluid altogether (or use {}) for an unbounded fluid expression, provide either side for a one-sided bound, and provide both only when you want clamp(). The only irreducible profile field is the designWidth used for conversion; when no other behaviour needs overriding, it can be shortened to profiles: { app: 375 }.
adaptiveMatrix({
defaultProfile: 'app',
profiles: {
app: { designWidth: 750, fluid: { minWidth: 320, maxWidth: 600 } },
},
})/* Input — 16px in all four places */
:root { --van-padding-md: 16px }
.van-cell { padding: 16px }
.el-input { padding: 16px }
.page-hero { padding: 16px }/* Output — each on the canvas it belongs to */
:root { --van-padding-md: clamp(13.65333px, 4.26667vw, 25.6px) }
.van-cell { padding: clamp(13.65333px, 4.26667vw, 25.6px) }
.el-input { padding: 16px }
.page-hero { padding: clamp(6.82667px, 2.13333vw, 12.8px) }Built in: vant, nutui, varlet, antd-mobile, taro-ui, element-plus, antd, arco-design, naive-ui, quasar, mui.
A library that is not listed takes one line to define; a listed one takes extends to adjust. See Component libraries.
/* adaptive-ignore-next */
width: 320px; /* skip the next declaration */
height: 44px; /* adaptive-ignore */ /* skip this line */
/* adaptive-ignore-rule */
.widget { width: 300px } /* skip the whole rule that follows */These comments survive into the output, so they still apply if the output is compiled again (a minifier removes them). Hairlines of 1px or less are left alone by default, and strings, url(), local() and format() are never converted by accident.
npx adaptive-matrix src/styles/app.csssrc/styles/app.css
profiles: app (default), pc, +5 library canvases
.page
padding 16px → clamp(13.65333px, 4.26667vw, 20.48px)
font-size 16px → clamp(0.94867rem, calc(0.65rem + 1.49333vw), 1.098rem)
@media (min-width: 768px) › .page
padding 48px → clamp(34.13333px, 3.33333vw, 64px)
shrinks .card font-size gets smaller at 768px: 17.57px → 16.18px
3 converted, 0 left as authored
No build to start, no browser to open.
That last line is the seam check. The app file says 16px and the desktop file says 18px; both are reasonable on their own. But the app canvas has already grown to 17.57px by the time it hands over, and the desktop canvas starts at 16.18px — so widening the window across the breakpoint makes the body text jump down. For an individual converted length on a fixed viewport canvas, its magnitude does not decrease as the viewport widens; a negative margin grows by becoming more negative. The checker compares magnitudes around known breakpoints and flags possible handoff problems, including authored CSS expressions. A finding is a reason to inspect the cascade and canvas selection, not proof that switching canvases is the only cause. Unresolved expressions and conditions can be skipped, so a clean report is not a layout guarantee.
--from also lets you rehearse file-based routing, which is the easiest thing to misconfigure and the hardest to notice. See CLI preview.
npx adaptive-matrix src/app.css --targets "ios_saf 13, chrome 90" needs @layer — iOS Safari 13 < 15.4, Chrome 90 < 99
if unsupported: The whole @layer block is dropped, so the entire root
foundation goes with it — the centred column, the safe-area variables
and the fixed-position correction all vanish at once.
instead: root.layer: false emits the same rules unwrapped. ...
needs clamp(), min(), max() — iOS Safari 13 < 13.4-13.7
CSS does not degrade gracefully; it degrades by discarding, and it never says so. An unreadable value takes its declaration, an unreadable selector takes its rule, an unreadable at-rule takes its whole block. So the audit reads the compiled stylesheet — not your configuration, which is the only way the two can never drift — and for each feature says how much disappears before it says what to switch off.
Support versions are baked in at build time, so this adds no runtime dependency and works offline. See Browser support and degradation.
| Getting started | From install to a two-design-file workflow |
| Build tool integration | Vite, Nuxt, Webpack, Taro — and four ways to fail silently |
| CLI preview | npx adaptive-matrix: conversions without a build |
| Component libraries | The built-in list, how matching works, overriding and extending |
| Configuration reference | Every option, its type and its default |
| Architecture and formulas | The pipeline, the maths, idempotence, and the limits |
| Optional runtime | The VisualViewport observer: keyboards, address bars, WebViews |
| Browser support and degradation | Feature × version matrix, what gets discarded, how to switch each one off |
| Migration guide | Coming from another px conversion setup |
| Release and compatibility | Artifacts, Node versions, versioning policy |
| Conformance suite | A language-agnostic behavioural specification |
| Full example | A runnable app + desktop project |
Every page is available in both English and Simplified Chinese; use the switcher at the top of each one.
- Node.js 18+, PostCSS 8.4+
clamp()andmin()are baseline in modern browsers- Container-query configurations need a browser with
@containerandcqi - For older WebViews use
strategy: 'viewport', optionally withpreserveOriginal: truefor a fallback declaration — run--targetsto see exactly what your output asks for
npm install
npm run check # types + coverage + build
npm run conformance:update
npm run benchMIT. See the contributing guide to get involved; please report security issues privately per the security policy.