A Tailwind CSS plugin that replaces stair-step breakpoints with smooth, mathematically perfect fluid scaling using CSS clamp().
Standard Tailwind uses adaptive design: values jump at fixed breakpoints (sm:, md:, lg:). Fluidwind uses fluid design: values scale linearly between any two viewport widths with zero JavaScript at runtime.
<!-- Before: 3 classes, 2 hard jumps -->
<h1 class="text-2xl md:text-4xl lg:text-6xl">Hello</h1>
<!-- After: 1 class, perfectly smooth -->
<h1 class="fw-text-[24px-60px]">Hello</h1>The CSS output is a single clamp() declaration:
font-size: clamp(1.5rem, 3.38vw + 0.77rem, 3.75rem);npm install fluidwindAdd the plugin to your Tailwind config:
// tailwind.config.js
module.exports = {
plugins: [require("fluidwind")],
};With a custom remBase (if your root font-size is not 16px):
plugins: [require("fluidwind")({ remBase: 10 })],// tailwind.config.js
module.exports = {
plugins: [require("fluidwind")],
theme: {
fluidwind: {
// Default viewport range (used when no modifier is provided)
defaultRange: ["375px", "1440px"],
// Named ranges - reference them with the /name modifier
ranges: {
post: ["600px", "1000px"],
wide: ["1024px", "1920px"],
},
// Named font-size scale — use as fw-text-md, fw-text-xl, etc.
fontSize: {
sm: ["14px", "18px"],
md: ["16px", "24px"],
lg: ["20px", "32px"],
xl: ["28px", "48px"],
"2xl": ["36px", "64px"],
},
// Shared spacing scale — drives padding, margin, gap, and sizing utilities
spacing: {
xs: ["4px", "8px"],
sm: ["8px", "16px"],
md: ["16px", "32px"],
lg: ["24px", "64px"],
},
},
},
};fw-{utility}-[{minValue}-{maxValue}]/{range}
| Part | Description | Example |
|---|---|---|
fw- |
Required prefix | fw- |
{utility} |
Any supported utility name | text, p, bg |
[{min}-{max}] |
Fluid range in any supported unit | [16px-32px] |
/{range} |
Optional - named or inline viewport range | /post, /[400-1200] |
<!-- font-size: clamp(1rem, 1.5vw + ..., 2rem) -->
<h1 class="fw-text-[16px-32px]">Fluid Heading</h1>
<!-- Using rem units -->
<p class="fw-text-[1rem-1.5rem]">Fluid Body</p><!-- padding -->
<section class="fw-p-[16px-64px]">...</section>
<!-- margin, shorthand and per-side -->
<div class="fw-mt-[8px-32px] fw-px-[16px-48px]">...</div><div class="fw-w-[200px-800px] fw-h-[100px-400px]">...</div><!-- Fluid negative margin for overlapping layouts -->
<div class="fw-mt-[-20px--60px]">...</div>Smoothly transition between two colors as the viewport grows:
<!-- background shifts from red → blue -->
<div class="fw-bg-[#ff0000-#0000ff]">...</div>
<!-- text shifts from black → white -->
<span class="fw-text-[#000000-#ffffff]">...</span>Generated CSS:
background-color: color-mix(
in srgb,
#ff0000 calc(clamp(0, 100 * (1440 - tan(atan2(100vw, 1px))) / 1065, 100) * 1%),
#0000ff
);<!-- Named range from config -->
<p class="fw-text-[16px-24px]/post">Post body text</p>
<!-- Inline viewport range -->
<p class="fw-text-[16px-24px]/[400-1200]">Custom range</p>Define named fluid values in your config and use them by key — no more repeating pixel ranges:
<!-- instead of fw-text-[16px-24px] everywhere -->
<h1 class="fw-text-xl">Heading</h1>
<p class="fw-text-md">Body text</p>
<!-- spacing scale drives padding, margin, gap, and sizing -->
<section class="fw-p-lg fw-gap-sm">...</section>Arbitrary [min-max] values still work alongside named keys — they are never replaced.
Fluidwind utilities compose with standard Tailwind breakpoints:
<p class="fw-text-[14px-18px] lg:fw-text-[18px-28px]">...</p>Install tailwind-merge and wrap your setup with withFluidwind() so fw-* classes correctly conflict with — and override — standard Tailwind classes (and vice-versa):
import { extendTailwindMerge } from 'tailwind-merge'
import { withFluidwind } from 'fluidwind/tailwind-merge'
export const twMerge = extendTailwindMerge(withFluidwind())
// twMerge('p-4 fw-p-[1rem-2rem]') → 'fw-p-[1rem-2rem]'
// twMerge('text-sm fw-text-[16px-32px]') → 'fw-text-[16px-32px]'
// twMerge('fw-gap-[4px-16px] gap-8') → 'gap-8'
// twMerge('fw-p-[4px-8px] fw-p-[8px-16px]') → 'fw-p-[8px-16px]'tailwind-merge is an optional peer dependency — only needed if you use class merging.
| Category | Utilities |
|---|---|
| Typography | fw-text-* |
| Padding | fw-p-* fw-px-* fw-py-* fw-pt-* fw-pb-* fw-pl-* fw-pr-* |
| Margin | fw-m-* fw-mx-* fw-my-* fw-mt-* fw-mb-* fw-ml-* fw-mr-* |
| Sizing | fw-w-* fw-h-* fw-min-w-* fw-max-w-* fw-min-h-* fw-max-h-* |
| Layout | fw-gap-* fw-gap-x-* fw-gap-y-* |
| Decoration | fw-border-* fw-rounded-* |
| Color | fw-bg-* fw-text-* |
Values can be expressed in any combination of: px, rem, vw, %, or unitless (treated as px).
<div class="fw-p-[10px-2rem]">Mix and match units</div>slope = (vMax - vMin) / (wMax - wMin)
intercept = vMin - slope * wMin
output = clamp(vMin_rem, slope×100vw + intercept_rem, vMax_rem)
All math runs in px internally; CSS output uses rem for accessibility (respects user font-size preferences).
All fluid numeric utilities rely on clamp() - supported in all modern browsers since 2020.
Color interpolation (fw-bg-*, fw-text-* with hex values) uses color-mix():
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| 111+ | 113+ | 16.2+ | 111+ |
Contributions are welcome. See CONTRIBUTING.md for setup instructions and guidelines.
Made with ❤️ by koyev