Skip to main content

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.

info

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

lib/utils.ts
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

components/Button.tsx
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:

lib/utils.ts
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:

tailwind.config.ts
import fluidTypography from 'fluid-typography'

const options = {
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
}

export default {
plugins: [fluidTypography(options)]
}
lib/utils.ts
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

lib/typography-config.ts
import type { PluginOptions } from 'fluid-typography'

export const typographyOptions: PluginOptions = {
customScales: {
'hero': { size: [50, 80] },
'mega': { size: [60, 120] }
}
}
tailwind.config.ts
import fluidTypography from 'fluid-typography'
import { typographyOptions } from './lib/typography-config'

export default {
plugins: [fluidTypography(typographyOptions)]
}
lib/utils.ts
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

components/ui/typography.tsx
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}
/>
)
}
app/page.tsx
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:

lib/utils.test.ts
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:

  1. Check import path - Use "fluid-typography/merge", not "fluid-typography"
  2. Verify installation - Make sure tailwind-merge is installed
  3. 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