API — framework adapters (React · Vue · Angular)
The constructs are framework-agnostic and identical everywhere: r$.fluid(16, 32) is the same value in all three. What an adapter adds is the lifecycle — apply when the element exists, update() when the declaration changes, dispose when the component goes away. Nothing else is wrapped, and nothing is hidden: you can always reach for the runtime directly.
Prefer prose? The lifecycle section of the runtime guide explains why handles exist; this page is the signatures.
| React | Vue | Angular | |
|---|---|---|---|
| package | @responsivejs/react | @responsivejs/vue | @responsivejs/angular |
| peer | react ≥ 18 | vue ≥ 3.4 | @angular/core ≥ 16 |
| style map | useResponsive(ref, map, deps?) | useResponsive(elRef, map | ref(map)) | injectResponsive(el, map) |
| geometry | useGeometry(ref, states, deps?) | useGeometry(elRef, states) | injectGeometry(el, states) |
| tokens | useTokens(map, deps?) | useTokens(map) | injectTokens(map) |
| group | useScope() | useScope() | injectScope() |
| width | useViewportWidth(): number | useViewportWidth(): ShallowRef<number> | injectViewportWidth(): Signal<number> |
| breakpoint | useBreakpoint(ref): boolean | useBreakpoint(ref): ShallowRef<boolean> | injectBreakpoint(ref): Signal<boolean> |
| template form | — | v-responsive="map" | (a 6-line directive in your app — below) |
React
const ref = useRef<HTMLDivElement>(null);
useResponsive(ref, { padding: r$.fluid(12, 24) }); // → clamp(), zero JS
useGeometry(ref, { wrapped: r$.whenWraps }); // → data-wrapped
const isDesktop = useBreakpoint('desktop');depsfollows theuseEffectconvention. Changing them callsupdate()on the live handle (properties the new map drops are restored) instead of tearing it down.useViewportWidth/useBreakpointread throughuseSyncExternalStore, so they are concurrent-safe and SSR-safe: the server snapshot isconfig().ssrWidth.- StrictMode's double-invocation does not duplicate constructs.
Vue
<script setup lang="ts">
const card = ref<HTMLElement | null>(null);
useResponsive(card, { padding: r$.fluid(12, 24) });
const isDesktop = useBreakpoint('desktop');
</script>
<template>
<div ref="card" v-responsive="{ gap: r$.fluid(8, 16) }" />
</template>- Pass a
ref(map)and the handle updates throughwatchwhen you replace it. - Template form:
app.use(responsivePlugin)registersv-responsiveglobally, or importvResponsiveinto a component'sdirectives. The directive owns the handle across mount, update and unmount.
Angular
The package ships no decorators on purpose: it needs no Angular compilation step (no ng-packagr, no partial-ivy artifacts), so it works from any Angular ≥ 16 app as plain functions and signals.
@Component({ selector: 'app-card', standalone: true, template: `<div #card>…</div>` })
export class CardComponent {
private readonly card = viewChild.required<ElementRef<HTMLElement>>('card');
readonly width = injectViewportWidth(); // Signal<number>
readonly isDesktop = injectBreakpoint('desktop'); // Signal<boolean>
constructor() {
afterNextRender(() => {
injectResponsive(this.card().nativeElement, { padding: r$.fluid(12, 24) });
});
}
}Every inject* helper must run in an injection context and releases on DestroyRef. Each has a DI-free twin — createResponsive, createGeometry, createViewportWidth, createBreakpoint — returning { update?, destroy } for code that owns its own teardown (and for use outside a constructor, e.g. inside afterNextRender).
Want the template form? Six lines in your app, compiled by your own build:
@Directive({ selector: '[rjsResponsive]', standalone: true })
export class RjsResponsiveDirective implements OnChanges, OnDestroy {
@Input('rjsResponsive') map: StyleMap = {};
private binding?: Binding<StyleMap>;
constructor(private readonly el: ElementRef<HTMLElement>) {}
ngOnChanges() { this.binding ? this.binding.update(this.map) : (this.binding = createResponsive(this.el, this.map)); }
ngOnDestroy() { this.binding?.destroy(); }
}What every adapter guarantees
- One construct per binding. An update never stacks a second construct: check
r$.manifest()— it stays at one entry. - Full restoration. Disposal restores inline values that existed before the construct, removes injected CSS and geometry attributes, and releases observers.
- No wrapping of values.
r$.fluid,r$.whenWraps,r$.breakpointsare imported from@responsivejs/runtimein all three — the adapter never re-exports a parallel API. - SSR-safe reads. Width readers fall back to
config().ssrWidthwhen there is no window.
For elements the adapter does not own (a portal, a third-party widget, a list rendered by another library), reach for r$.observe(selector, map) from the runtime: it keeps a selector bound as elements mount and unmount.