From harness-claude
Implements fluid typography using CSS clamp() with viewport-relative scaling, minimum readable sizes, and hierarchy across breakpoints.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:design-responsive-typeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Type across viewports — fluid typography with CSS clamp(), viewport-relative scaling, minimum readable sizes, and maintaining hierarchy across breakpoints
Type across viewports — fluid typography with CSS clamp(), viewport-relative scaling, minimum readable sizes, and maintaining hierarchy across breakpoints
Understand the core problem. Fixed font sizes create two failure modes:
Use CSS clamp() for fluid typography. The clamp() function accepts three values: minimum, preferred (fluid), and maximum:
/* Font scales from 16px (mobile) to 20px (desktop) */
font-size: clamp(1rem, 0.8rem + 0.5vw, 1.25rem);
The preferred value (0.8rem + 0.5vw) creates a smooth scaling curve. The rem component provides a stable base; the vw component adds viewport-proportional growth. The clamp bounds prevent the font from going below 16px or above 20px.
Calculate the preferred value using the fluid type formula. Given:
min-size: the smallest font size (in px)max-size: the largest font size (in px)min-vw: the viewport width where min-size applies (typically 320px)max-vw: the viewport width where max-size applies (typically 1240px or 1440px)The formula:
preferred = min-size + (max-size - min-size) * ((100vw - min-vw) / (max-vw - min-vw))
In CSS-friendly form (converting to rem + vw):
slope = (max-size - min-size) / (max-vw - min-vw)
intercept = min-size - (slope * min-vw)
preferred = intercept(rem) + slope(vw)
Worked example — 16px to 20px between 320px and 1240px viewports:
slope = (20 - 16) / (1240 - 320) = 4 / 920 = 0.00435 = 0.435vw
intercept = 16 - (0.00435 * 320) = 16 - 1.391 = 14.609px = 0.913rem
Result: clamp(1rem, 0.913rem + 0.435vw, 1.25rem)
You do not need to calculate this by hand — use Utopia.fyi or similar calculators. But understanding the math prevents blind copy-pasting of values you cannot debug.
Apply fluid scaling to your entire type scale. Each step in your type scale gets its own clamp() with proportional min/max values:
| Token | Mobile (320px) | Desktop (1240px) | CSS |
|---|---|---|---|
| text-sm | 14px | 14px | 0.875rem (no scaling needed) |
| text-base | 16px | 18px | clamp(1rem, 0.957rem + 0.217vw, 1.125rem) |
| text-lg | 18px | 22px | clamp(1.125rem, 1.038rem + 0.435vw, 1.375rem) |
| text-xl | 22px | 28px | clamp(1.375rem, 1.245rem + 0.652vw, 1.75rem) |
| text-2xl | 28px | 40px | clamp(1.75rem, 1.489rem + 1.304vw, 2.5rem) |
| text-3xl | 36px | 56px | clamp(2.25rem, 1.815rem + 2.174vw, 3.5rem) |
Key principle: larger type scales more aggressively. Body text grows by 2px (16->18), but display text grows by 20px (36->56). This maintains the hierarchy ratio — if everything grew by the same absolute amount, headings would lose prominence on desktop.
Enforce minimum readable sizes. These are non-negotiable floors:
Decide between fluid scaling and breakpoint scaling. Both approaches are valid:
Fluid scaling (clamp):
Breakpoint scaling (media queries):
Hybrid approach (recommended for most projects):
Utopia (utopia.fyi) is the most rigorous fluid type system in production use. Its methodology:
Utopia's key insight: the type scale ratio itself can change between viewports. A tighter ratio on mobile (1.2 minor third) prevents headings from overwhelming small screens, while a wider ratio on desktop (1.25 major third) creates more dramatic hierarchy on large screens. This means the hierarchy does not just scale — it adapts.
Example output from Utopia for a 5-step scale:
:root {
--step--1: clamp(0.8333rem, 0.7981rem + 0.1757vw, 1rem); /* 13-16px */
--step-0: clamp(1rem, 0.9348rem + 0.3261vw, 1.25rem); /* 16-20px */
--step-1: clamp(1.2rem, 1.0893rem + 0.5536vw, 1.5625rem); /* 19-25px */
--step-2: clamp(1.44rem, 1.2631rem + 0.8844vw, 1.9531rem); /* 23-31px */
--step-3: clamp(1.728rem, 1.4583rem + 1.3484vw, 2.4414rem); /* 28-39px */
--step-4: clamp(2.0736rem, 1.6766rem + 1.985vw, 3.0518rem); /* 33-49px */
}
Material Design 3 uses discrete breakpoints rather than fluid scaling:
| Window Class | Width Range | Body | Headline Large | Display Large |
|---|---|---|---|---|
| Compact | 0-599px | 14px | 28px | 45px |
| Medium | 600-839px | 14px | 32px | 52px |
| Expanded | 840px+ | 16px | 32px | 57px |
Material's reasoning: their component system uses fixed-width layout patterns (cards, rails, drawers), so typography tied to those fixed patterns is more predictable than fluid values. This is a valid engineering trade-off — fluid is not always better.
Tailwind provides responsive font sizes through breakpoint prefixes:
<h1 class="text-2xl md:text-4xl lg:text-5xl">Responsive Heading</h1>
This maps to:
Tailwind does not include fluid typography by default, but it integrates well with clamp:
// tailwind.config.js
fontSize: {
'fluid-base': 'clamp(1rem, 0.913rem + 0.435vw, 1.25rem)',
'fluid-lg': 'clamp(1.25rem, 1.076rem + 0.87vw, 1.75rem)',
'fluid-xl': 'clamp(1.75rem, 1.315rem + 2.174vw, 3.5rem)',
}
Pure viewport units (vw) have critical problems:
2vw = 4px — microscopic and unreadable2vw = 76.8px — comically oversizedvw with rem in the preferred value (0.5rem + 1vw), and always wrap in clamp() with rem-based min/max. The rem component ensures zoom still works.Pure vw units without clamp bounds. font-size: 3vw produces 9.6px on a 320px phone (illegible) and 57.6px on a 1920px desktop (absurd). This is the most common fluid typography mistake. Every viewport-relative size must have minimum and maximum bounds via clamp().
Too many breakpoint overrides. A heading that changes size at 5 breakpoints (480px, 640px, 768px, 1024px, 1280px) is a maintenance burden and creates 5 jarring size transitions. Fluid typography with clamp() eliminates breakpoint overrides for font sizes entirely. If you must use breakpoints, limit to 2 (mobile and desktop).
Shrinking body text below 14px on any viewport. Some responsive systems scale body text to 12px or 13px on mobile "to fit more content." This sacrifices readability — the primary purpose of text. If content does not fit at 16px, the layout needs adjustment, not the font size. Minimum body text is 16px on all viewports.
Non-proportional scaling that destroys hierarchy. When headings scale from 48px to 24px (50% reduction) but body text stays fixed at 16px, the heading-to-body ratio drops from 3:1 to 1.5:1. At mobile sizes, the heading barely stands out. Fix: scale all text levels proportionally — if headings shrink 33%, body should shrink ~10-15% to maintain the ratio difference (just less aggressively).
Forgetting line-height needs to scale too. A heading at 48px with line-height: 1.1 (52.8px leading) works beautifully. That same heading scaled to 24px on mobile still has line-height: 1.1 (26.4px leading), which is too tight for a smaller size that now wraps to more lines. Use fluid line-height: line-height: clamp(1.1, 1.0 + 0.25vw, 1.3) or set line-height per breakpoint.
Implementing Utopia for a Marketing Site Requirements: hero headline 36-72px, subheadline 20-32px, body 16-20px, caption 14-16px. Viewport range: 320px to 1440px.
:root {
--text-caption: clamp(0.875rem, 0.8315rem + 0.2174vw, 1rem);
--text-body: clamp(1rem, 0.913rem + 0.4348vw, 1.25rem);
--text-sub: clamp(1.25rem, 0.9891rem + 1.3043vw, 2rem);
--text-hero: clamp(2.25rem, 1.4674rem + 3.913vw, 4.5rem);
}
Hierarchy ratios:
Converting a Breakpoint System to Fluid Before (5 breakpoints):
h1 {
font-size: 24px;
}
@media (min-width: 480px) {
h1 {
font-size: 28px;
}
}
@media (min-width: 640px) {
h1 {
font-size: 32px;
}
}
@media (min-width: 768px) {
h1 {
font-size: 36px;
}
}
@media (min-width: 1024px) {
h1 {
font-size: 42px;
}
}
@media (min-width: 1280px) {
h1 {
font-size: 48px;
}
}
After (1 line):
h1 {
font-size: clamp(1.5rem, 0.978rem + 2.609vw, 3rem);
}
Same visual result (24px at 320px, 48px at 1280px), smooth transitions at every viewport in between, one declaration instead of six.
This is a knowledge skill. When activated, it provides responsive typography expertise to guide font-size declarations across viewports. Use these principles when implementing CSS clamp() values, configuring Tailwind fontSize with fluid values, or auditing responsive type behavior. Cross-reference with design-type-scale for base scale ratios and design-readability for line-length interaction at different viewport sizes.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeImplements fluid typography using CSS clamp() that scales with viewport. Use when creating responsive font sizes, viewport-aware headings, or type that adapts smoothly without breakpoints.
Guides web typography with 2-3 font family limits, modular scales (Minor Third, Major Third, Perfect Fourth), line heights (1.4-1.6 body, 1.1-1.3 headings), and 45-75 character line lengths per Bringhurst rules.
Designs accessible typography systems using relative units, scalable type scales, and user-override-safe spacing. Useful for setting font sizes, line heights, and ensuring WCAG readability compliance.