Fluid Responsive Design - Typography
Overview
The useFontSizeDesignTokens() composable produces font-size variables from a mix of static values and fluid pixel ranges. Tuple [min, max] and object { min, max } values are clamped between viewport breakpoints; strings, numbers, and @-references stay fixed. Static and fluid entries can coexist in the same call.
Unlike traditional responsive typography that jumps between fixed sizes at breakpoints, fluid typography creates smooth, continuous transitions that look perfect at every viewport width.
Why use fluid typography?
Fluid typography helps you:
- Eliminate breakpoint jumps: Text scales smoothly instead of jumping between fixed sizes at media queries.
- Maintain perfect proportions: Use modular scales to ensure harmonious relationships at every viewport width.
- Reduce code complexity: Define your entire type system once instead of managing multiple breakpoints.
- Improve readability: Automatically optimize text size for the available space.
- Create responsive hierarchies: Scale relationships between different text sizes remain consistent.
Mixing fluid and static font sizes
Pass each size key one of: a static value (string, number, or @-reference), a [min, max] tuple of absolute pixel values, or a { min, max } object. Range values interpolate across the viewport between the breakpoint reference, which defaults to @fluid.breakpoint from useFluidViewportDesignTokens().
import { styleframe } from 'styleframe';
import {
useFluidViewportDesignTokens,
useFontSizeDesignTokens,
} from '@styleframe/theme';
const s = styleframe();
useFluidViewportDesignTokens(s);
const {
fontSize,
fontSizeSm,
fontSizeMd,
fontSizeLg,
} = useFontSizeDesignTokens(s, {
default: '@font-size.md',
sm: '0.875rem', // fixed
md: [16, 18], // fluid: clamp 16px → 18px
lg: { min: 18, max: 24 }, // fluid (object form)
});
export default s;
:root {
--fluid--min-width: 320;
--fluid--max-width: 1440;
--fluid--screen: 100vw;
--fluid--breakpoint: calc(...);
--font-size--sm: 0.875rem;
--font-size--md: calc((16 / 16 * 1rem) + (18 - 16) * var(--fluid--breakpoint));
--font-size--lg: calc((18 / 16 * 1rem) + (24 - 18) * var(--fluid--breakpoint));
--font-size: var(--font-size--md);
}
useFluidViewportDesignTokens() before using fluid ranges. The default fluid.breakpoint reference comes from there. You can also pass a custom breakpoint via the options bag: useFontSizeDesignTokens(s, values, { breakpoint: customRef }).Pairing fluid ranges with modular scales
To preserve a live link to a modular scale (so changing the scale changes every fluid font size), define the pixel base as min/max keys and build the ranges from CSS expressions that multiply @font-size.min/@font-size.max by @scale.min-powers.<n> / @scale.max-powers.<n>. The default useDesignTokensPreset configuration uses this exact pattern, so the live link survives any later override of useScaleDesignTokens or useScalePowersDesignTokens.
import { styleframe } from 'styleframe';
import {
useFluidViewportDesignTokens,
useFontSizeDesignTokens,
useScaleDesignTokens,
useScalePowersDesignTokens,
} from '@styleframe/theme';
const s = styleframe();
useFluidViewportDesignTokens(s);
const { scaleMin, scaleMax } = useScaleDesignTokens(s, {
min: '@scale.minor-third',
max: '@scale.major-third',
});
useScalePowersDesignTokens(s, scaleMin); // emits scale.min-powers.<n>
useScalePowersDesignTokens(s, scaleMax); // emits scale.max-powers.<n>
useFontSizeDesignTokens(s, {
min: 16, // --font-size--min (configurable base)
max: 18, // --font-size--max (configurable base)
default: '@font-size.md',
xs: ['@font-size.min * @scale.min-powers.-2', '@font-size.max * @scale.max-powers.-2'],
sm: ['@font-size.min * @scale.min-powers.-1', '@font-size.max * @scale.max-powers.-1'],
md: ['@font-size.min * @scale.min-powers.0', '@font-size.max * @scale.max-powers.0'],
lg: ['@font-size.min * @scale.min-powers.1', '@font-size.max * @scale.max-powers.1'],
xl: ['@font-size.min * @scale.min-powers.2', '@font-size.max * @scale.max-powers.2'],
});
Understanding Modular Scales
Modular scales create harmonious proportions by multiplying a base value by consistent ratios. The scale ratio determines how dramatically your font sizes grow.
Common Scale Ratios
| Scale Name | Ratio | Character | Best For |
|---|---|---|---|
| Minor Second | 1.067 | Very subtle | Minimal designs, tight hierarchies |
| Major Second | 1.125 | Subtle | Clean, understated designs |
| Minor Third | 1.2 | Balanced | Most websites, readable hierarchies |
| Major Third | 1.25 | Distinct | Marketing sites, clear hierarchy |
| Perfect Fourth | 1.333 | Bold | Editorial content, strong contrast |
| Perfect Fifth | 1.5 | Dramatic | Landing pages, hero sections |
| Golden Ratio | 1.618 | Striking | Art, design-forward sites |
Choosing Scale Ratios
Choose different scales for mobile and desktop to optimize readability at different viewport sizes:
import { useScaleDesignTokens } from '@styleframe/theme';
const s = styleframe();
const { scaleMin, scaleMax } = useScaleDesignTokens(s, {
min: '@scale.minor-third', // Minor Third scale for mobile
max: '@scale.major-third' // Major Third scale for desktop
});
Best Practices
- Choose appropriate base size range: Keep body text scaling subtle for better readability. 16px to 18px (12.5% increase) is ideal. Avoid extreme ranges like 12px to 20px (67% increase) which harm readability.
- Use consistent scale ratios: Stick to proven modular scales. Common combinations include Minor Third to Major Third (1.2 to 1.25), or Minor Third to Perfect Fourth (1.2 to 1.333).
- Ensure mobile scale is smaller than or equal to desktop scale.
- Always call
useFluidViewportDesignTokens()first: Fluid ranges reference@fluid.breakpointby default. - Limit the number of font sizes: Aim for a smaller number of distinct sizes to maintain clear hierarchy without creating confusion. Too many sizes (12+) lead to inconsistency.
- Test at multiple viewports: Verify typography at mobile, tablet, and desktop sizes. Ensure body text remains readable with 45-75 characters per line.
FAQ
md: [16, 18]) inside useFontSizeDesignTokens() for typography systems — the composable owns the variable name and integrates with the static scale.useFluidClamp(s, [min, max]) directly for one-off fluid values on any CSS property (spacing, border radius, etc.).useFontSizeDesignTokens() call. Order does not matter and the two forms can interleave.useLineHeightDesignTokens() to get consistent ratios like lineHeightTight (1.25), lineHeightNormal (1.5), and lineHeightRelaxed (1.75). When you apply these to elements with fluid font sizes, the line height automatically scales proportionally, maintaining readable spacing at all viewport sizes without additional calculations.useFontSizeDesignTokens() with useFontFamilyDesignTokens() and useFontWeightDesignTokens() to create sophisticated typography systems. Variable fonts offer the added benefit of smooth weight transitions and optical sizing adjustments that complement the fluid scaling, resulting in even better typographic quality across viewport sizes.useFluidViewportDesignTokens(s, { minWidth: 375, maxWidth: 1920 }) if needed. The key is choosing a range that matches your actual user viewport distribution.Next Steps
- Clamp Function: Learn about
useFluidViewportDesignTokens()anduseFluidClamp() - Scales: Deep dive into modular scales and scale powers
- Typography: Explore the static font-size composable and other typography tokens
- Line Height: Create proportional line heights
- Utopia Fluid Type Scale: Explore the mathematical foundation behind fluid typography