tailwind-merge Integration
Learn how to integrate Fluid Typography with tailwind-merge for proper class merging.
Overview
If you use tailwind-merge in your project (commonly via a cn() utility), you'll want to extend it to support fluid typography classes. This ensures proper conflict resolution between typography classes.
Skip this if you're not using tailwind-merge. This integration is only needed if you're already using tailwind-merge in your project.
Why This Is Needed
Without integration, tailwind-merge doesn't know that fluid typography classes conflict with each other:
// Without integration - both classes applied ❌
cn('text-h1 text-body')
// Output: "text-h1 text-body" (both applied - wrong!)
// With integration - last class wins ✅
cn('text-h1 text-body')
// Output: "text-body" (correct!)
Basic Setup
1. Install tailwind-merge
npm install tailwind-merge clsx
2. Create or Update Utils File
import { clsx, type ClassValue } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";
import { getFluidTypographyMergeConfig } from "fluid-typography/merge";
// Create extended merge with fluid typography support
const twMerge = extendTailwindMerge(getFluidTypographyMergeConfig());
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3. Use in Components
import { cn } from "@/lib/utils"
export function Button({ className, ...props }) {
return (
<button
className={cn("text-body font-medium", className)}
{...props}
/>
)
}
Now classes merge correctly:
<Button className="text-h1">
{/* Output: text-h1 font-medium */}
</Button>
With Custom Scales
If you're using custom scales, pass the same options to getFluidTypographyMergeConfig:
import { clsx, type ClassValue } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";
import { getFluidTypographyMergeConfig } from "fluid-typography/merge";
const twMerge = extendTailwindMerge(
getFluidTypographyMergeConfig({
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
})
);
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Now your custom classes merge properly:
cn('text-hero text-h1') // => 'text-h1' ✅
cn('text-mega text-body') // => 'text-body' ✅
cn('text-h1 text-hero') // => 'text-hero' ✅ (last wins)
Important: Keep Options Synchronized
The options passed to getFluidTypographyMergeConfig must match those passed to the plugin:
import fluidTypography from 'fluid-typography'
const options = {
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
}
export default {
plugins: [fluidTypography(options)]
}
import { getFluidTypographyMergeConfig } from "fluid-typography/merge"
// Use the SAME options
const options = {
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
}
const twMerge = extendTailwindMerge(
getFluidTypographyMergeConfig(options)
)
Better: Share Configuration
import type { PluginOptions } from 'fluid-typography'
export const typographyOptions: PluginOptions = {
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
}
import fluidTypography from 'fluid-typography'
import { typographyOptions } from './lib/typography-config'
export default {
plugins: [fluidTypography(typographyOptions)]
}
import { extendTailwindMerge } from "tailwind-merge"
import { getFluidTypographyMergeConfig } from "fluid-typography/merge"
import { typographyOptions } from './typography-config'
const twMerge = extendTailwindMerge(
getFluidTypographyMergeConfig(typographyOptions)
)
Examples
Component Library Pattern
import { cn } from "@/lib/utils"
export function H1({ className, ...props }) {
return (
<h1
className={cn("text-h1", className)}
{...props}
/>
)
}
export function Body({ className, ...props }) {
return (
<p
className={cn("text-body", className)}
{...props}
/>
)
}
import { H1, Body } from "@/components/ui/typography"
export default function Page() {
return (
<>
{/* Override with different scale */}
<H1 className="text-display-xl">Hero Title</H1>
{/* Override with custom scale */}
<Body className="text-body-sm">Smaller text</Body>
</>
)
}
Conditional Styling
import { cn } from "@/lib/utils"
function Card({ featured, children }) {
return (
<div className={cn(
"text-body",
featured && "text-h2" // Properly replaces text-body
)}>
{children}
</div>
)
}
With Variants (CVA)
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const headingVariants = cva("font-bold", {
variants: {
size: {
large: "text-h1",
medium: "text-h2",
small: "text-h3"
}
},
defaultVariants: {
size: "medium"
}
})
export function Heading({ size, className, ...props }) {
return (
<h1
className={cn(headingVariants({ size }), className)}
{...props}
/>
)
}
{/* Classes merge correctly */}
<Heading size="large" className="text-h3">
{/* Output: text-h3 font-bold */}
</Heading>
Testing Integration
Verify that your integration is working:
import { cn } from './utils'
test('fluid typography classes merge correctly', () => {
expect(cn('text-h1 text-body')).toBe('text-body')
expect(cn('text-body text-h1')).toBe('text-h1')
expect(cn('text-h1 font-bold text-h2')).toBe('font-bold text-h2')
})
Troubleshooting
Classes Not Merging
If classes aren't merging:
- Check import path - Use
"fluid-typography/merge", not"fluid-typography" - Verify installation - Make sure
tailwind-mergeis installed - Check options - Ensure options match between plugin and merge config
Custom Classes Not Recognized
If custom classes aren't being recognized:
// ❌ Wrong - no options passed
const twMerge = extendTailwindMerge(getFluidTypographyMergeConfig())
// ✅ Correct - pass same options as plugin
const twMerge = extendTailwindMerge(
getFluidTypographyMergeConfig({
customScales: {
'hero': { size: [50, 80] }
}
})
)
Next Steps
- Tailwind CSS v4 Guide - Tailwind v4 integration
- Customization Guide - Customize typography
- FAQ - Common questions