The Typography Problem
You're building a hero section. The heading looks perfect on desktop (48px), but on mobile it's way too big. You add a media query:
h1 {
font-size: 48px;
}
@media (max-width: 768px) {
h1 {
font-size: 32px;
}
}
Then your designer asks for a tablet size. And a small desktop size. And a large desktop size. Soon you have:
h1 {
font-size: 32px; /* Mobile */
}
@media (min-width: 640px) {
h1 {
font-size: 36px; /* Tablet portrait */
}
}
@media (min-width: 768px) {
h1 {
font-size: 40px; /* Tablet landscape */
}
}
@media (min-width: 1024px) {
h1 {
font-size: 48px; /* Desktop */
}
}
@media (min-width: 1536px) {
h1 {
font-size: 56px; /* Large desktop */
}
}
Five breakpoints. Five font sizes. One heading.
Now multiply this by every text element in your design system (headings, body text, captions, labels). You end up with hundreds of media queries and a maintenance nightmare.
There's a better way: fluid typography with clamp() + design tokens.
TL;DR
- Problem: Traditional responsive typography requires multiple breakpoints and media queries
- Solution: CSS
clamp()creates fluid scaling between min/max sizes - Formula:
font-size: clamp(minSize, preferredSize, maxSize) - Design tokens store fluid type scales centrally and make them reusable
- Benefits: Fewer breakpoints, smoother scaling, easier maintenance
- AI-friendly: Tokens with clamp() values help AI generate responsive typography automatically
- Best practice: Define fluid type scales as tokens, generate CSS variables + Tailwind utilities
What is Fluid Typography?
Fluid typography means font sizes scale smoothly with viewport width, without discrete breakpoints.
Traditional (Stepped)
Mobile Tablet Desktop
32px → 40px → 48px
| | |
640px 1024px 1536px
Typography "jumps" at each breakpoint.
Fluid (Smooth)
Mobile → ········ → Desktop
32px gradually 48px
| increases |
640px 1536px
Typography scales continuously between min and max.
CSS clamp() Explained
The clamp() function takes three values:
font-size: clamp(MIN, PREFERRED, MAX);
- MIN: Minimum size (e.g., 32px) — never smaller than this
- PREFERRED: Ideal size using viewport units (e.g., 4vw) — scales with screen width
- MAX: Maximum size (e.g., 48px) — never larger than this
Example:
h1 {
font-size: clamp(32px, 4vw, 48px);
}
How it works:
| Viewport Width | Calculation | Actual Size |
|---|---|---|
| 400px | 4vw = 16px | 32px (clamped to min) |
| 800px | 4vw = 32px | 32px |
| 1000px | 4vw = 40px | 40px |
| 1200px | 4vw = 48px | 48px (clamped to max) |
| 1600px | 4vw = 64px | 48px (clamped to max) |
Between 800px and 1200px, the heading scales smoothly. Outside that range, it clamps to min/max.
Visual Comparison
Traditional breakpoints:
Size
▲
48│ ┌─────────
│ │
40│ ┌──────┘
│ │
32│──────────────┘
└──────────────────────────────► Viewport
640px 1024px 1536px
Typography steps at breakpoints—visible jumps when resizing.
Fluid typography:
Size
▲
48│ ╱────────────
│ ╱
40│ ╱
│ ╱
32│────────╱
└──────────────────────────────► Viewport
640px 1024px 1536px
Typography scales smoothly—no visible jumps.
Building a Fluid Type Scale with Tokens
Step 1: Define Base Sizes
Start with min/max sizes for each heading level:
{
"typography": {
"heading": {
"h1": {
"min": { "value": "32px", "type": "dimension" },
"max": { "value": "48px", "type": "dimension" }
},
"h2": {
"min": { "value": "28px", "type": "dimension" },
"max": { "value": "40px", "type": "dimension" }
},
"h3": {
"min": { "value": "24px", "type": "dimension" },
"max": { "value": "32px", "type": "dimension" }
}
},
"body": {
"lg": {
"min": { "value": "16px", "type": "dimension" },
"max": { "value": "18px", "type": "dimension" }
},
"base": {
"min": { "value": "14px", "type": "dimension" },
"max": { "value": "16px", "type": "dimension" }
}
}
}
}
Step 2: Calculate Fluid Values
Use this formula to calculate the viewport-relative value:
preferredSize = minSize + (maxSize - minSize) * ((100vw - minViewport) / (maxViewport - minViewport))
For h1 (32px → 48px, scaling between 640px and 1536px):
preferredSize = 32px + (48 - 32) * ((100vw - 640px) / (1536 - 640))
= 32px + 16 * ((100vw - 640px) / 896)
= 32px + 1.786vw - 11.43px
= 20.57px + 1.786vw
Simplified:
font-size: clamp(32px, 1.786vw + 20.57px, 48px);
Step 3: Store as Token
FramingUI supports composite tokens:
{
"typography": {
"heading": {
"h1": {
"fontSize": {
"value": "clamp(32px, 1.786vw + 20.57px, 48px)",
"type": "dimension",
"description": "Fluid h1 size (32px-48px, scales 640px-1536px)"
}
}
}
}
}
Step 4: Generate CSS Variables
npx framingui build
Output:
:root {
--typography-heading-h1-font-size: clamp(32px, 1.786vw + 20.57px, 48px);
}
h1 {
font-size: var(--typography-heading-h1-font-size);
}
Now every <h1> scales fluidly. No breakpoints needed.
Practical Fluid Type Scale
Here's a complete responsive type scale:
{
"typography": {
"heading": {
"h1": {
"fontSize": { "value": "clamp(2rem, 1.786vw + 1.286rem, 3rem)" }
},
"h2": {
"fontSize": { "value": "clamp(1.75rem, 1.339vw + 1.161rem, 2.5rem)" }
},
"h3": {
"fontSize": { "value": "clamp(1.5rem, 0.893vw + 1.107rem, 2rem)" }
},
"h4": {
"fontSize": { "value": "clamp(1.25rem, 0.446vw + 1.054rem, 1.5rem)" }
}
},
"body": {
"lg": {
"fontSize": { "value": "clamp(1rem, 0.223vw + 0.929rem, 1.125rem)" }
},
"base": {
"fontSize": { "value": "clamp(0.875rem, 0.223vw + 0.804rem, 1rem)" }
},
"sm": {
"fontSize": { "value": "clamp(0.75rem, 0.112vw + 0.721rem, 0.875rem)" }
}
}
}
}
Generated CSS:
:root {
--font-size-h1: clamp(2rem, 1.786vw + 1.286rem, 3rem);
--font-size-h2: clamp(1.75rem, 1.339vw + 1.161rem, 2.5rem);
--font-size-h3: clamp(1.5rem, 0.893vw + 1.107rem, 2rem);
--font-size-body-lg: clamp(1rem, 0.223vw + 0.929rem, 1.125rem);
--font-size-body-base: clamp(0.875rem, 0.223vw + 0.804rem, 1rem);
}
h1 { font-size: var(--font-size-h1); }
h2 { font-size: var(--font-size-h2); }
h3 { font-size: var(--font-size-h3); }
Result: Perfectly scaled typography across all devices with zero media queries.
Advanced: Fluid Line Height & Letter Spacing
Font size isn't the only thing that should scale. Line height and letter spacing should adapt too.
Fluid Line Height
Smaller text needs more relative line-height for readability:
{
"typography": {
"body": {
"base": {
"fontSize": { "value": "clamp(0.875rem, 0.223vw + 0.804rem, 1rem)" },
"lineHeight": {
"value": "clamp(1.4, 0.5vw + 1.3, 1.6)",
"type": "number",
"description": "Scales from 1.4 (mobile) to 1.6 (desktop)"
}
}
}
}
}
Result:
- Mobile (small text): 1.4 line-height (tighter, more compact)
- Desktop (larger text): 1.6 line-height (more breathing room)
Fluid Letter Spacing
Larger headings need tighter tracking:
{
"typography": {
"heading": {
"h1": {
"fontSize": { "value": "clamp(2rem, 1.786vw + 1.286rem, 3rem)" },
"letterSpacing": {
"value": "clamp(-0.02em, -0.01vw + -0.015em, -0.01em)",
"type": "dimension",
"description": "Tighter tracking on larger screens"
}
}
}
}
}
This creates a more refined, editorial feel as text scales up.
Using Fluid Typography in Components
React Component Example
export function Hero({ title, subtitle }) {
return (
<section className="py-16 px-4">
<h1 className="font-bold text-heading-h1 leading-tight tracking-tight">
{title}
</h1>
<p className="mt-4 text-body-lg text-text-secondary">
{subtitle}
</p>
</section>
);
}
Tailwind config (generated from tokens):
module.exports = {
theme: {
fontSize: {
'heading-h1': 'clamp(2rem, 1.786vw + 1.286rem, 3rem)',
'body-lg': 'clamp(1rem, 0.223vw + 0.929rem, 1.125rem)',
}
}
}
Result: Heading and subtitle scale smoothly across all screen sizes. No breakpoints in your component code.
Common Pitfalls
Pitfall 1: Overly Aggressive Scaling
/* ❌ Bad: scales too fast */
font-size: clamp(16px, 10vw, 64px);
On a 1200px viewport: 10vw = 120px → clamped to 64px (max).
But at 700px: 10vw = 70px → still 64px (max).
Problem: Font stays at max size for a wide range, then suddenly drops. Not smooth.
Solution: Use smaller viewport units (1-3vw):
/* ✅ Good: gradual scaling */
font-size: clamp(16px, 2vw + 12px, 64px);
Pitfall 2: Too Many Fluid Scales
You don't need clamp() for everything.
Use fluid scaling for:
- ✅ Headings (h1-h4)
- ✅ Hero section text
- ✅ Display text (large, prominent text)
Keep fixed for:
- ✅ Body text (reading comfort matters more than scaling)
- ✅ UI labels (buttons, badges, tabs)
- ✅ Small text (captions, footnotes)
Example:
{
"typography": {
"heading": {
"h1": { "fontSize": "clamp(2rem, 2vw + 1rem, 3rem)" } // Fluid
},
"body": {
"base": { "fontSize": "1rem" } // Fixed
},
"label": {
"base": { "fontSize": "0.875rem" } // Fixed
}
}
}
Pitfall 3: Forgetting Accessibility
Users can change their browser's default font size (usually 16px). Fluid typography should respect this.
Bad (px-based):
font-size: clamp(32px, 4vw, 48px);
If user sets browser font size to 20px, your heading stays 32-48px. Doesn't scale with user preference.
Good (rem-based):
font-size: clamp(2rem, 4vw, 3rem);
If user sets browser font size to 20px:
2rem = 40px3rem = 60px
Your heading scales proportionally. Accessible by default.
Always use rem for min/max values in clamp():
/* ✅ Good */
font-size: clamp(1rem, 2vw + 0.5rem, 2rem);
/* ❌ Bad */
font-size: clamp(16px, 2vw + 8px, 32px);
Calculating Fluid Typography
The Manual Way
Formula:
clamp(MIN, (MIN + (MAX - MIN) * (100vw - MIN_VP) / (MAX_VP - MIN_VP)), MAX)
Example: Scale from 32px (640px viewport) to 48px (1536px viewport):
Slope = (48 - 32) / (1536 - 640) = 16 / 896 = 0.01786
Y-intercept = 32 - (0.01786 * 640) = 20.57
Result: clamp(32px, 1.786vw + 20.57px, 48px)
Problem: This math is tedious and error-prone.
The Easy Way: Use a Tool
Online calculators:
Input: Min/max sizes, min/max viewports
Output: clamp() formula
FramingUI (coming soon):
npx framingui generate-fluid-scale \
--min-size 32px \
--max-size 48px \
--min-viewport 640px \
--max-viewport 1536px
Output:
{
"typography": {
"heading": {
"h1": {
"fontSize": {
"value": "clamp(2rem, 1.786vw + 1.286rem, 3rem)",
"type": "dimension"
}
}
}
}
}
Design Token Structure for Typography
Basic Typography Tokens
{
"typography": {
"fontFamily": {
"base": { "value": "Inter, sans-serif", "type": "fontFamily" },
"heading": { "value": "Cal Sans, Inter, sans-serif", "type": "fontFamily" },
"mono": { "value": "Fira Code, monospace", "type": "fontFamily" }
},
"fontSize": {
"h1": { "value": "clamp(2rem, 2vw + 1rem, 3rem)", "type": "dimension" },
"h2": { "value": "clamp(1.5rem, 1.5vw + 0.75rem, 2.25rem)", "type": "dimension" },
"body": { "value": "1rem", "type": "dimension" }
},
"fontWeight": {
"normal": { "value": "400", "type": "fontWeight" },
"medium": { "value": "500", "type": "fontWeight" },
"semibold": { "value": "600", "type": "fontWeight" },
"bold": { "value": "700", "type": "fontWeight" }
},
"lineHeight": {
"tight": { "value": "1.25", "type": "number" },
"normal": { "value": "1.5", "type": "number" },
"relaxed": { "value": "1.75", "type": "number" }
},
"letterSpacing": {
"tight": { "value": "-0.02em", "type": "dimension" },
"normal": { "value": "0", "type": "dimension" },
"wide": { "value": "0.05em", "type": "dimension" }
}
}
}
Composite Text Styles
Group font properties together:
{
"typography": {
"style": {
"heading-1": {
"fontFamily": { "value": "{typography.fontFamily.heading}" },
"fontSize": { "value": "{typography.fontSize.h1}" },
"fontWeight": { "value": "{typography.fontWeight.bold}" },
"lineHeight": { "value": "{typography.lineHeight.tight}" },
"letterSpacing": { "value": "{typography.letterSpacing.tight}" }
},
"body": {
"fontFamily": { "value": "{typography.fontFamily.base}" },
"fontSize": { "value": "{typography.fontSize.body}" },
"fontWeight": { "value": "{typography.fontWeight.normal}" },
"lineHeight": { "value": "{typography.lineHeight.normal}" },
"letterSpacing": { "value": "{typography.letterSpacing.normal}" }
}
}
}
}
Generated CSS
FramingUI transforms these into CSS variables + utility classes:
:root {
--font-family-heading: Cal Sans, Inter, sans-serif;
--font-size-h1: clamp(2rem, 2vw + 1rem, 3rem);
--font-weight-bold: 700;
--line-height-tight: 1.25;
--letter-spacing-tight: -0.02em;
}
.text-heading-1 {
font-family: var(--font-family-heading);
font-size: var(--font-size-h1);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: var(--letter-spacing-tight);
}
Usage:
<h1 className="text-heading-1">Welcome to FramingUI</h1>
One class. Fully responsive. Scales smoothly.
Integrating with Tailwind
Tailwind doesn't natively support clamp() in fontSize, but you can extend it:
// tailwind.config.js
module.exports = {
theme: {
fontSize: {
'heading-1': 'clamp(2rem, 2vw + 1rem, 3rem)',
'heading-2': 'clamp(1.5rem, 1.5vw + 0.75rem, 2.25rem)',
'body': '1rem',
'body-lg': 'clamp(1rem, 0.5vw + 0.875rem, 1.125rem)',
},
extend: {
// Optionally add line-height and letter-spacing per text style
lineHeight: {
'tight': '1.25',
'normal': '1.5',
},
letterSpacing: {
'tight': '-0.02em',
'normal': '0',
}
}
}
}
Usage:
<h1 className="text-heading-1 leading-tight tracking-tight">
Welcome
</h1>
AI Code Generation with Fluid Typography
Without Typography Tokens
Prompt: "Create a hero section with a heading and subtitle"
AI output:
<section className="py-20 text-center">
<h1 className="text-5xl font-bold">Welcome to Our Product</h1>
<p className="text-xl text-gray-600 mt-4">Build faster with AI</p>
</section>
Problems:
text-5xlis Tailwind's default (3rem fixed) — not responsivetext-xl(1.25rem fixed) — doesn't scale- Doesn't match your design system's fluid scale
With Typography Tokens
AI reads your tokens via MCP:
{
"typography": {
"style": {
"hero-heading": {
"fontSize": "clamp(2rem, 2vw + 1rem, 3rem)",
"description": "Large hero heading with fluid scaling"
},
"hero-subtitle": {
"fontSize": "clamp(1rem, 0.5vw + 0.875rem, 1.25rem)",
"description": "Hero subtitle text"
}
}
}
}
AI output:
<section className="py-20 text-center">
<h1 className="text-hero-heading font-bold">Welcome to Our Product</h1>
<p className="text-hero-subtitle text-text-secondary mt-4">
Build faster with AI
</p>
</section>
Result: Perfect responsive typography, matching your design system, generated automatically.
Viewport-Based vs. Container-Based
Viewport-Based (Standard)
font-size: clamp(2rem, 2vw, 3rem);
Scales based on viewport width (entire browser window).
Best for:
- Page-level headings
- Hero sections
- Full-width content
Container-Based (Modern)
font-size: clamp(2rem, 5cqi, 3rem);
Scales based on container width (parent element), using Container Queries.
Best for:
- Cards inside grids (card width varies)
- Sidebar content (scales with sidebar width)
- Reusable components that appear in different layouts
Browser support: Chrome 105+, Firefox 110+, Safari 16+ (2023+)
FramingUI support (coming soon):
{
"typography": {
"card": {
"heading": {
"fontSize": {
"value": "clamp(1.25rem, 5cqi, 1.75rem)",
"type": "dimension",
"description": "Scales with card width"
}
}
}
}
}
Debugging Fluid Typography
Issue: Text Too Large on Mobile
Symptom: Heading is 48px on mobile, should be 32px.
Cause: Min value in clamp() is too high, or viewport unit is too large.
Fix:
/* ❌ Before */
font-size: clamp(48px, 4vw, 64px); /* Min is 48px */
/* ✅ After */
font-size: clamp(32px, 4vw, 64px); /* Min reduced to 32px */
Issue: Text Doesn't Scale
Symptom: Heading stays the same size on all screens.
Cause: Viewport unit is too small, or min/max are identical.
Fix:
/* ❌ Before */
font-size: clamp(32px, 0.1vw + 31.5px, 32.5px); /* Barely scales */
/* ✅ After */
font-size: clamp(32px, 2vw + 16px, 48px); /* Clear scaling range */
Issue: Text Scales Too Fast
Symptom: On a 1920px screen, heading is gigantic (80px+).
Cause: Max value is too high, or viewport unit is too large.
Fix:
/* ❌ Before */
font-size: clamp(32px, 5vw, 96px); /* Max too high */
/* ✅ After */
font-size: clamp(32px, 2vw + 16px, 56px); /* Max capped */
Debugging Tool
Use browser DevTools to inspect computed values:
// In browser console
const h1 = document.querySelector('h1');
console.log(getComputedStyle(h1).fontSize);
Resize window → check if font size changes smoothly.
Real-World Example: Building a Landing Page
Traditional Approach (Breakpoints)
<section className="py-12 md:py-16 lg:py-20">
<h1 className="text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-bold">
Build Faster with AI
</h1>
<p className="text-base md:text-lg lg:text-xl text-gray-600 mt-4">
Design tokens that AI actually understands
</p>
<button className="mt-8 text-sm md:text-base lg:text-lg px-6 py-3">
Get Started
</button>
</section>
Problems:
- 12+ responsive class names for one section
- Typography "jumps" at breakpoints
- Hard to maintain (every text element needs 4 size variants)
Fluid Approach (Tokens + clamp)
Define tokens:
{
"typography": {
"hero": {
"heading": {
"fontSize": { "value": "clamp(2rem, 3vw + 1rem, 4rem)" }
},
"subtitle": {
"fontSize": { "value": "clamp(1rem, 1vw + 0.75rem, 1.5rem)" }
}
}
},
"spacing": {
"section": {
"padding-y": { "value": "clamp(3rem, 5vw, 6rem)" }
}
}
}
Component:
<section className="py-section">
<h1 className="text-hero-heading font-bold">
Build Faster with AI
</h1>
<p className="text-hero-subtitle text-text-secondary mt-4">
Design tokens that AI actually understands
</p>
<button className="mt-8 text-base px-6 py-3">
Get Started
</button>
</section>
Result:
- 7 fewer responsive classes
- Smooth scaling (no jumps)
- Easier to maintain (change tokens, not components)
FramingUI's Fluid Typography Workflow
Step 1: Define Fluid Scale
npx framingui init --fluid-typography
This generates a starter fluid type scale:
{
"typography": {
"fontSize": {
"h1": { "value": "clamp(2rem, 2vw + 1rem, 3rem)" },
"h2": { "value": "clamp(1.75rem, 1.5vw + 1rem, 2.5rem)" },
"h3": { "value": "clamp(1.5rem, 1vw + 1rem, 2rem)" },
"body-lg": { "value": "clamp(1rem, 0.5vw + 0.875rem, 1.125rem)" },
"body": { "value": "1rem" }
}
}
}
Step 2: Build CSS + Tailwind
npx framingui build
Output:
/* CSS variables */
:root {
--font-size-h1: clamp(2rem, 2vw + 1rem, 3rem);
--font-size-h2: clamp(1.75rem, 1.5vw + 1rem, 2.5rem);
--font-size-body-lg: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
}
/* Utility classes */
.text-h1 { font-size: var(--font-size-h1); }
.text-h2 { font-size: var(--font-size-h2); }
.text-body-lg { font-size: var(--font-size-body-lg); }
// Tailwind config
module.exports = {
theme: {
fontSize: {
'h1': 'var(--font-size-h1)',
'h2': 'var(--font-size-h2)',
'body-lg': 'var(--font-size-body-lg)',
}
}
}
Step 3: Use in Components
export function Hero({ title, subtitle }) {
return (
<section className="py-20">
<h1 className="text-h1 font-bold leading-tight">
{title}
</h1>
<p className="text-body-lg text-text-secondary mt-4">
{subtitle}
</p>
</section>
);
}
Step 4: AI Generates Responsive Typography Automatically
When AI reads your design tokens via MCP, it sees:
typography.fontSize.h1: "clamp(2rem, 2vw + 1rem, 3rem)"
typography.fontSize.body-lg: "clamp(1rem, 0.5vw + 0.875rem, 1.125rem)"
AI understands: "These are responsive sizes—I should use them for headings and body text."
AI output:
<h1 className="text-h1 font-bold">AI-Generated Heading</h1>
<p className="text-body-lg">AI-generated content scales responsively</p>
No manual responsive classes. AI generates fluid typography from tokens.
Best Practices
1. Start with a Modular Scale
Use a mathematical ratio (1.25, 1.333, 1.5) to generate harmonious sizes:
Base: 16px (1rem)
Ratio: 1.25
Scale:
- 16px (1rem)
- 20px (1.25rem)
- 25px (1.563rem)
- 31px (1.953rem)
- 39px (2.441rem)
- 49px (3.052rem)
Then apply clamp() to each step:
{
"fontSize": {
"xs": "0.75rem", // Fixed
"sm": "0.875rem", // Fixed
"base": "1rem", // Fixed
"lg": "clamp(1rem, 0.5vw + 0.875rem, 1.25rem)",
"xl": "clamp(1.25rem, 1vw + 0.938rem, 1.563rem)",
"2xl": "clamp(1.563rem, 1.5vw + 1rem, 2rem)",
"3xl": "clamp(2rem, 2vw + 1rem, 2.5rem)"
}
}
2. Document Intended Use
Add descriptions so developers (and AI) know when to use each size:
{
"typography": {
"fontSize": {
"h1": {
"value": "clamp(2rem, 2vw + 1rem, 3rem)",
"description": "Large page headings, hero titles (mobile: 32px, desktop: 48px)"
}
}
}
}
3. Test Across Real Devices
Fluid typography looks different on actual phones vs. browser DevTools.
Test on:
- iPhone SE (375px) — smallest common phone
- iPhone 14 Pro (430px) — average phone
- iPad (768px) — tablet
- MacBook (1440px) — laptop
- Desktop (1920px+) — large screens
Make sure text is readable at each size.
4. Combine with Fluid Spacing
Typography looks best when spacing scales proportionally:
{
"spacing": {
"section": {
"padding-y": { "value": "clamp(3rem, 5vw, 6rem)" }
}
},
"typography": {
"fontSize": {
"h1": { "value": "clamp(2rem, 2vw + 1rem, 3rem)" }
}
}
}
Result: Heading and section padding scale together—maintains visual balance.
Tools for Generating Fluid Scales
1. Utopia
Input:
- Min/max font size
- Min/max viewport
- Type scale ratio
Output: Full clamp() formulas + CSS
2. Fluid Type Scale Calculator
Visual editor for fluid scales. Adjust sliders → see live preview.
3. FramingUI CLI (Coming Soon)
npx framingui generate-type-scale \
--base-size 16px \
--ratio 1.25 \
--min-viewport 640px \
--max-viewport 1536px \
--fluid-levels h1,h2,h3
Generates JSON tokens with clamp() formulas.
Conclusion
Fluid typography with design tokens eliminates breakpoint hell:
Traditional approach:
- ❌ 5+ media queries per text element
- ❌ Typography "jumps" at breakpoints
- ❌ Hundreds of responsive class names
- ❌ Hard to maintain, easy to forget breakpoints
Token-based fluid approach:
- ✅ One clamp() formula per text style
- ✅ Smooth scaling across all devices
- ✅ One class per element (
.text-h1) - ✅ Centralized in design tokens
- ✅ AI generates responsive typography automatically
Start simple:
- Identify your most-used text styles (h1, h2, body)
- Define min/max sizes for each
- Calculate clamp() formulas (use Utopia or FramingUI)
- Store as design tokens
- Generate CSS variables + Tailwind utilities
- Replace breakpoint-based typography with fluid classes
Your typography will scale smoothly, your code will be cleaner, and AI will generate perfectly responsive text on the first try.
Ready to eliminate media query hell? Try FramingUI.