reflow
July 27, 2026 ยท View on GitHub
reflow
SSR-safe responsive toolkit for TypeScript. One API, every framework.
Works in React, Vue, Svelte, Solid, Qwik, Preact, Lit, Angular, and plain JS. No hydration mismatch warnings. No typeof window checks scattered everywhere. No framework lock-in. One import. One type system.
npm install usereflow
What is this?
Reflow gives you breakpoints, container queries, fluid typography, viewport tracking, and user preference detection in one library. Same API in React, Vue, Svelte, Solid, Qwik, Preact, Lit, Angular, and plain JS. No hydration mismatch warnings. No typeof window checks scattered everywhere.
One import. One type system. Zero framework lock-in. Switch frameworks without rewriting your responsive logic.
Quick start
React
import { ResponsiveProvider, useBreakpoint, useResponsiveValue, Show } from "reflow/react";
import { fluidClamp } from "reflow/styles";
function App() {
const bp = useBreakpoint();
const cols = useResponsiveValue({ xs: 1, md: 2, xl: 4 });
return (
<main style={{ fontSize: fluidClamp({ minPx: 16, maxPx: 22 }) }}>
<p>Breakpoint: {bp.active}</p>
<Grid columns={cols} />
<Show above="md">
<Sidebar />
</Show>
<Show below="md" fallback={<DesktopNav />}>
<MobileMenu />
</Show>
</main>
);
}
export default () => (
<ResponsiveProvider serverWidth={1024}>
<App />
</ResponsiveProvider>
);
Vue 3
<script setup lang="ts">
import { useBreakpoint } from "reflow/vue";
const bp = useBreakpoint();
</script>
<template>
<FullNav v-if="bp.above('desktop')" />
<HamburgerMenu v-else />
</template>
Svelte
<script>
import { breakpoint } from "reflow/svelte";
const bp = breakpoint();
</script>
{#if $bp === 'desktop'}
<FullNav />
{:else}
<HamburgerMenu />
{/if}
Vanilla JS
import { createBreakpoints, observeViewport } from "reflow";
const bp = createBreakpoints({ sm: 640, md: 768, lg: 1024 } as const);
const unsub = observeViewport(({ width }) => {
console.log(bp.resolve(width));
});
unsub();
Framework support
| Framework | Entry point | Status |
|---|---|---|
| React | reflow/react | Full hooks + components |
| Vue 3 | reflow/vue | Composables + plugin |
| Svelte 5 | reflow/svelte | Stores |
| Solid | reflow/solid | Core + responsive hooks |
| Qwik | reflow/qwik | Core functions |
| Preact | reflow/preact | React-compatible |
| Angular | reflow/angular | Core functions |
| Lit | reflow/lit | Core functions |
All adapters share the same framework-agnostic core. React, Vue, and Svelte ship dedicated entry points. Everything else uses the core directly.
Why reflow?
Most responsive libraries do one thing. A media query hook here, a viewport hook there, a fluid type calculator somewhere else. You end up with 5 packages, overlapping logic, and hydration errors to debug.
Reflow puts it all in one place:
- SSR-safe by default. Every hook uses
useSyncExternalStorewithgetServerSnapshot. Pair with<ResponsiveProvider serverWidth={...}>for correct first paint. - Typed breakpoints.
createBreakpoints({ sm: 640, md: 768 } as const)gives you literal-typed keys with full autocomplete. - Fluid typography.
fluidClamp()generates CSSclamp()at runtime. No more copy-pasting from utopia.fyi. - Container queries. ResizeObserver-based, no polyfill needed. Works in any framework.
- User preferences.
prefers-reduced-motion,prefers-reduced-data,prefers-contrast,forced-colors,inverted-colors. All typed, all SSR-safe. - Server rendering.
resolveBreakpointFromHints(headers)reads Client Hints with UA fallback. Works with Next.js, Hono, Express, anything. - Zero framework lock-in. Core has no React dependency. Use it in Vue, Svelte, Solid, or plain JS.
How does reflow compare?
See the full comparison against react-responsive, @vueuse/core, and usehooks-ts.
What's included
| Module | Exports |
|---|---|
Core (reflow) | breakpoints, viewport, media queries, container queries, preferences, pointer, DPR, safe area |
Styles (reflow/styles) | fluidClamp, fluidScale, containerQuery, responsiveStyle, safeArea, dvh/svh/lvh |
Server (reflow/server) | resolveBreakpointFromHints, resolveBreakpointFromUA, clientHintsResponseHeaders |
Testing (reflow/testing) | installMatchMediaMock, installResizeObserverMock, setWindowSize |
Tailwind (reflow/tailwind) | tailwindPreset |
SSR integration
Next.js App Router
import { headers } from "next/headers";
import { resolveBreakpointFromHints } from "reflow/server";
import { ResponsiveProvider } from "reflow/react";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const h = await headers();
const { width } = resolveBreakpointFromHints(h);
return (
<ResponsiveProvider serverWidth={width}>
{children}
</ResponsiveProvider>
);
}
Express / Hono / any server
import { resolveServerBreakpoint, clientHintsResponseHeaders } from "reflow/server";
app.use((req, res, next) => {
for (const [key, value] of clientHintsResponseHeaders) {
res.setHeader(key, value);
}
next();
});
app.get("/", (req, res) => {
const { breakpoint, width } = resolveServerBreakpoint(req.headers);
});
Bundle size
| Entry | Min + gzip |
|---|---|
reflow (core) | ~2.4 KB |
reflow/react | ~3.3 KB |
reflow/styles | ~1.3 KB |
reflow/server | ~0.8 KB |
Enforced in CI via size-limit. Every PR that exceeds the budget fails.
Testing
Reflow ships test utilities so your component tests don't need a real browser:
import { installMatchMediaMock, installResizeObserverMock, setWindowSize } from "reflow/testing";
const matchMedia = installMatchMediaMock();
const resizeObserver = installResizeObserverMock();
setWindowSize(768, 1024);
matchMedia.set("(prefers-color-scheme: dark)", true);
resizeObserver.resize(element, { width: 500, height: 300 });
API reference
Full API docs are in the source. Each module exports typed functions with JSDoc. Here are the main ones:
Core exports
| Export | Description |
|---|---|
createBreakpoints(map) | Create a typed breakpoint system with resolve, up, down, between, only |
defaultBreakpoints | { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, "2xl": 1536 } |
watchMedia(query) | SSR-safe matchMedia wrapper |
observeViewport(listener) | Subscribe to window resize/orientation |
getViewport() | Snapshot { width, height, orientation } |
observeContainer(el, listener) | ResizeObserver-based container subscription |
getContainerSize(el) | Sync container size snapshot |
matchesContainerRange(size, range) | Check if container matches { minPx?, maxPx? } |
observePreference(key, listener) | Watch reduced-motion, dark mode, forced-colors, etc. |
getAllPreferences() | Snapshot of all preference booleans |
observePointerCapabilities(listener) | Watch hover/coarse/fine pointer changes |
observeDevicePixelRatio(listener) | Watch DPR changes |
observeSafeArea(listener) | Watch env(safe-area-inset-*) |
resolveResponsive(system, value, width) | Pick value from breakpoint-keyed map |
createFluidityStore(system, opts) | Shared reactive store for any framework |
React exports
| Export | Type | Description |
|---|---|---|
ResponsiveProvider | component | Context provider with serverWidth, serverHeight, system |
useBreakpoint() | hook | { active, is, above, below, between } |
useMediaQuery(query) | hook | SSR-safe matchMedia boolean |
useViewport() | hook | { width, height, orientation } |
useResponsiveValue(map) | hook | Resolve breakpoint-keyed values |
usePreference(key) | hook | reduced-motion, dark, forced-colors, etc. |
usePointer() | hook | { hover, coarse, fine } |
useDevicePixelRatio() | hook | Current DPR |
useSafeArea() | hook | { top, right, bottom, left } in px |
useContainerQuery(ref, range) | hook | Boolean, does container match range? |
useContainerSize(ref) | hook | { width, height } of container |
useDynamicViewport() | hook | { dvh, svh, lvh } in px |
useElementSize(ref) | hook | { width, height } of any element |
Show / Hide | component | Conditional render by breakpoint |
BreakpointBadge | component | Dev overlay, auto-hidden in production |
Styles exports
| Export | Description |
|---|---|
fluidClamp(opts) | Generate CSS clamp() for fluid sizing |
fluidScale(steps, opts) | Build a named fluid type scale |
containerQuery(opts) | Build @container rule string |
responsiveStyle(system, prop, values) | Breakpoint to media-query style objects |
safeAreaInset(side, fallbackPx) | CSS env(safe-area-inset-*) with fallback |
dvh / svh / lvh | Dynamic viewport unit helpers |
visuallyHidden | Screen-reader-only style object |
logical | Physical to logical property name map |
Browser support
| Browser | Minimum |
|---|---|
| Chrome / Edge | Last 2 versions |
| Firefox | Last 2 versions |
| Safari | 16+ |
Container queries require Safari 16+, Chromium 105+, Firefox 110+. prefers-reduced-data is Chromium-only and gracefully returns false elsewhere.
Development
git clone https://github.com/valtors/reflow && cd reflow
npm install
npm run verify # typecheck + lint + test + build + publint + attw + size
Roadmap
Shipped:
- Core responsive primitives (breakpoints, viewport, container, media, preferences)
- React, Vue, Svelte, Solid, Qwik, Preact, Angular, Lit adapters
- Fluid typography helpers
- Server-side rendering utilities
- Tailwind preset
- Test utilities
- CI with typecheck, lint, test, build, publint, attw, size-limit
- Throttle/debounce support on viewport and container observers
Next:
- Custom hook generators (define your own responsive hooks from config)
- DevTools browser extension
- More framework-specific examples and starter templates
- Migration guides from react-responsive, @vueuse/core, usehooks-ts
Contributing
Check CONTRIBUTING.md for the full guide. Look for good first issue labels to get started.
We follow the Contributor Covenant.
License
MIT (c) Tamish Mhatre
If reflow saves you time, star the repo.
Companion Project
Relay - a local-first MCP server with 40+ tools for file ops, web fetch, screenshots, agent memory, and more. Same org, same philosophy: boring tech that just works.
Star History
Sponsors
If reflow helps your work, consider giving it a star. It helps others discover it.