Smoother responsive styles in fewer classes.

A fluid clamp() plugin for Tailwind that works with every utility.

Get started
index.html
<main class="~p-6/10 p-6 md:p-8 lg:p-10">
<div class="max-w-4xl mx-auto grid grid-cols-1 lg:max-w-5xl lg:gap-x-20 lg:grid-cols-2">
<div class="relative p-3 col-start-1 row-start-1 flex flex-col-reverse rounded-lg bg-gradient-to-t from-black/75 via-black/0 sm:bg-none sm:row-start-2 sm:p-0 lg:row-start-1">
<h1 class="mt-1 ~text-lg/2xl text-lg md:text-xl lg:text-2xl font-semibold text-white sm:text-slate-900 dark:sm:text-white">Beach House in Collingwood</h1>
<p class="text-sm leading-4 font-medium text-white sm:text-slate-500 dark:sm:text-slate-400">Entire house</p>
</div>
<div class="grid gap-4 col-start-1 col-end-3 row-start-1 sm:mb-6 sm:grid-cols-4 lg:gap-6 lg:col-start-2 lg:row-end-6 lg:row-span-6 lg:mb-0">
<img src="/beach-house.jpg" alt="..." class="w-full h-60 object-cover rounded-lg sm:h-52 sm:col-span-2 lg:col-span-full" loading="lazy">
<img src="/beach-house-interior-1.jpg" alt="..." class="hidden w-full h-52 object-cover rounded-lg sm:block sm:col-span-2 md:col-span-1 lg:row-start-2 lg:col-span-2 lg:h-32" loading="lazy">
<img src="/beach-house-interior-2.jpg" alt="..." class="hidden w-full h-52 object-cover rounded-lg md:block lg:row-start-2 lg:col-span-2 lg:h-32" loading="lazy">
</div>
<dl class="~mt-4/2.5 mt-4 lg:mt-2.5 text-xs font-medium flex items-center row-start-2 sm:row-start-3 lg:row-start-2">
<dt class="sr-only">Reviews</dt>
<dd class="flex items-center">
<svg width="24" height="24" fill="none" aria-hidden="true" class="mr-1 stroke-current">
<path d="m12 5 2 5h5l-4 4 2.103 5L12 16l-5.103 3L9 14l-4-4h5l2-5Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span>4.89 <span class="text-slate-400 font-normal">(128)</span></span>
</dd>
<dt class="sr-only">Location</dt>
<dd class="flex items-center">
<svg width="2" height="2" aria-hidden="true" fill="currentColor" class="mx-3 text-slate-300">
<circle cx="1" cy="1" r="1" />
</svg>
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1 text-slate-400 dark:text-slate-500" aria-hidden="true">
<path d="M18 11.034C18 14.897 12 19 12 19s-6-4.103-6-7.966C6 7.655 8.819 5 12 5s6 2.655 6 6.034Z" />
<path d="M14 11a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z" />
</svg>
Collingwood, Ontario
</dd>
</dl>
<div class="mt-4 col-start-1 row-start-3 self-center sm:mt-0 sm:col-start-2 sm:row-start-2 sm:row-span-2 lg:mt-6 lg:col-start-1 lg:row-start-3 lg:row-end-4">
<button type="button" class="bg-sky-500 text-white text-sm leading-6 font-medium py-2 px-3 rounded-lg">Check availability</button>
</div>
<p class="mt-4 text-sm leading-6 col-start-1 sm:col-span-2 lg:mt-6 lg:row-start-4 lg:col-span-1 dark:text-slate-400">
This sunny and spacious room is for those traveling light and looking for a comfy and cosy place to lay their head for a night or two. This beach house sits in a vibrant neighborhood littered with cafes, pubs, restaurants and supermarkets and is close to all the major attractions such as Edinburgh Castle and Arthur's Seat.
</p>
</div>
</main>
build.css
.\~p-6\/10{
padding: clamp(1.5rem, 1.15rem + 1.74vw, 2.5rem)
}
.\~text-lg\/2xl {
font-size: clamp(1.125rem, 0.9rem + 0.75vw, 1.5rem);
line-height: clamp(1.75rem, 1.6rem + 0.5vw, 2rem)
}
/* ... */

Features

  • Supports all core plugins out of the box (i.e. font size, margin, padding, width, etc.)
  • Fewer classes than breakpoints (i.e. pt-4 md:p-8 lg:pt-10)
  • Full Intellisense support
  • Ensures all fluid type meets accessibility requirements
  • Comes with a fluidize method to add fluid versions of any custom plugin
  • Flexible enough to handle advanced use cases

Installation

  1. Install the package

    Install fluid-tailwind via npm.

    Terminal window
    npm install -D fluid-tailwind
  2. Add the extractor

    The custom extractor lets you use the new ~ prefix in your Tailwind classes.

    tailwind.config.ts
    import { fluidExtractor } from 'fluid-tailwind'
    export default {
    content: {
    files: [/* ... */],
    extract: fluidExtractor()
    },
    // ...
    }
  3. Add the fluid core plugins

    Add fluid versions of every enabled core plugin.

    tailwind.config.ts
    import { fluidExtractor, fluidCorePlugins } from 'fluid-tailwind'
    export default {
    // ...
    plugins: [
    fluidCorePlugins
    ]
    }
  4. (Optional) Use rem screens and font sizes

    If you’re using Tailwind’s default fontSize and screens, you can convert them to rem to ensure greater compatibility with core plugins.

    This may be unnecessary in future versions of Tailwind

    tailwind.config.ts
    import { fluidExtractor, fluidCorePlugins, defaultThemeScreensInRems, defaultThemeFontSizeInRems } from 'fluid-tailwind'
    export default {
    // ...
    theme: {
    fontSize: defaultThemeFontSizeInRems,
    screens: defaultThemeScreensInRems,
    extend: {
    fontSize: {
    // ...
    }
    }
    }
    }

Basic usage

Your browser isn't wide enough to see the full effect

<button class="bg-sky-500 ~px-4/8 ~py-2/4 ~text-sm/xl ...">Fluid button</button>

Here’s a quick overview:

  • The ~ prefix makes a utility fluid
  • Fluid utilities have a start and end value, separated by a /
  • Fluid utilities interpolate between their start and end value when the viewport is between the start and end point, respectively
  • The start and end points default to the smallest and largest screen, but they can be customized or overridden per-utility

Limitations

All lengths must have the same unit

Due to CSS restrictions, fluid expressions can only be computed if all involved lengths have the same unit. This includes:

  • Start value / end value
  • Start point / end point

Trying to interpolate between two different units

<h1 class="~text-[1rem]/[24px]">

Trying to set font-size in rem when the screens are in px

<h1 class="~text-lg/xl">
tailwind.config.ts
export default {
// ...
theme: {
screens: {
'sm': '320px',
'2xl': '1280px'
},
fontSize: {
'lg': '1.5rem',
'xl': '2rem'
}
}
}

Using expressions like calc()

<h1 class="~text-base/[calc(1.5rem-2px)]">

Trying to interpolate between non-lengths like colors

<h1 class="~text-white/red-500">

Negative values

You can’t use the dash prefix - to negate fluid utilities like you can with i.e. -mt-3, because Tailwind would only negate the start value.

Trying to use - to negate a fluid utility

<div class="-~mt-3/5">

Configuration

Customizing default screens

The default start and end screens can be set with a tuple [start, end], where each value is a simple screen width. Either value can be omitted, in which case the plugin will use your smallest and largest screen, respectively.

tailwind.config.ts
import { ..., type FluidConfig } from 'fluid-tailwind'
export default {
theme: {
fluid: {
defaultScreens: ['20rem', '64rem']
} satisfies FluidConfig
}
}

Advanced

Customize screens per-utility

You can change the start/end screens for an individual fluid utility with the included ~ variant. For example:

Your browser isn't wide enough to see the full effect

Quick increase
<h1 class="~md/lg:~text-base/4xl">Quick increase</h1>

You can omit either start or end screen to use your global defaults:

Set start screen to md, end screen as default

<div class="~md:~text-base/4xl">

Set end screen to lg, start screen as default

<div class="~/lg:~text-base/4xl">

Arbitrary start screens

If you want to set an arbitrary start screen with the ~ variant, you have to use ~min-[] (just as you’d have to use min-[] to set an arbitrary screen breakpoint):

Trying to use ~[]: to set an arbitrary start screen

<div class="~[20rem]/lg:~text-base/4xl">

Using ~min-[] to set an arbitrary start screen

<div class="~min-[20rem]/lg:~text-base/4xl">

Container queries

If you have the official container query plugin installed, you can make the start/end points relative to the nearest @container rather than the viewport by using the ~@ variant:

<h1 class="~@md/lg:~text-base/4xl">Relative to container</h1>

This may look confusing if you use named containers. Sorry about that; there’s only so many ways to get values into Tailwind. In general, when you see the fluid ~ prefix, you know the / denotes a start/end pair.

Just like the ~ variant, both start and end containers are optional and will use your global defaults if unset.

Set end container to lg, start container as default

<div class="~@/lg:~text-base/4xl">

Trying to reference named containers

<div class="@container/main">
<h1 class="~@/3xl/main:~text-lg/base">

Browser support for named containers in fluid CSS expressions should arrive soon

Customizing default containers

The default containers can be set in the same way as screens. Either value can be omitted, in which case the plugin will use your smallest and largest container, respectively.

tailwind.config.ts
import { ..., type FluidConfig } from 'fluid-tailwind'
export default {
theme: {
fluid: {
defaultContainers: [, '30rem']
} satisfies FluidConfig
}
}

Fluidize other plugins

When adding custom plugins to your tailwind.config.js, you can run them through the fluidize function to automatically add fluid versions of all their utilities and components:

tailwind.config.ts
import { ..., fluidize } from 'fluid-tailwind'
import myFavoritePlugin from '...'
export default {
// ...
plugins: [
fluidize(myFavoritePlugin)
// ...
]
}

Combining with breakpoints

To really get crazy, you can combine fluid values with container or media queries, as such:

Your browser isn't wide enough to see the full effect

Whoa!
<h1 class="~/md:~text-base/4xl lg:~lg:~text-4xl/base">Whoa!</h1>

Here’s how this works:

  1. By default, we interpolate our font-size between base and 4xl until the viewport is our md screen
  2. When we get to our lg screen, we interpolate between the opposite 4xl and base, starting when the viewport is our lg screen

Custom prefix and/or separator

If you’re using a custom separator or prefix in your Tailwind config, you’ll need to pass them in to the custom extractor as well:

tailwind.config.ts
import { fluidExtractor } from 'fluid-tailwind'
export default {
content: {
files: [/* ... */],
extract: fluidExtractor({
prefix: 'tw-',
separator: '_'
})
},
prefix: 'tw-',
separator: '_',
// ...
}

Use a custom prefix with fluid type

<div class="tw-~text-sm/xl">