Utopia.fyi fluid typography and spacing system using clamp(), modular scales, and proportional design...
Systematic fluid typography and spacing using Utopia.fyi philosophy
Utopia represents a declarative design approach where you establish foundational rules at two poles (@min and @max viewports) and let the browser interpolate fluidly between them. This eliminates arbitrary breakpoints in favor of proportional, mathematical harmony.
clamp()Key Insight: You're not designing for specific devicesβyou're establishing a proportional system that scales gracefully across all viewport widths.
Rather than arbitrary font sizes at breakpoints, Utopia uses modular scalesβmathematical ratios applied repeatedly to create harmonious typography.
1.2 - Minor Third (subtle scaling)1.25 - Major Third (balanced, recommended)1.333 - Perfect Fourth (common choice)1.5 - Perfect Fifth (dramatic)1.618 - Golden Ratio (natural harmony)Define @min viewport (e.g., 320px):
Define @max viewport (e.g., 1500px):
Generated Scale:
Step -2: 11px β 13px
Step -1: 13px β 16px
Step 0: 16px β 20px (body text)
Step 1: 19px β 25px
Step 2: 23px β 31px
Step 3: 28px β 39px
Step 4: 34px β 49px
Step 5: 40px β 61px
:root {
/* Fluid type scale using clamp() */
--step--2: clamp(0.6944rem, 0.6578rem + 0.1831vi, 0.8rem);
--step--1: clamp(0.8333rem, 0.7813rem + 0.2604vi, 1rem);
--step-0: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--step-1: clamp(1.2rem, 1.1036rem + 0.4821vi, 1.5625rem);
--step-2: clamp(1.44rem, 1.3113rem + 0.6435vi, 1.9531rem);
--step-3: clamp(1.728rem, 1.5585rem + 0.8476vi, 2.4414rem);
--step-4: clamp(2.0736rem, 1.8517rem + 1.1094vi, 3.0518rem);
--step-5: clamp(2.4883rem, 2.2008rem + 1.4372vi, 3.8147rem);
}
/* Usage - semantic, not arbitrary */
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-3); }
h4 { font-size: var(--step-2); }
h5 { font-size: var(--step-1); }
body { font-size: var(--step-0); }
small { font-size: var(--step--1); }
.caption { font-size: var(--step--2); }
clamp(MIN, PREFERRED, MAX)
Example:
--step-0: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
1rem - Smallest value (at @min viewport)0.9286rem + 0.3571vi - Fluid calculation1.25rem - Largest value (at @max viewport)Preferred Calculation:
base + (slope Γ viewport-inline-size)
Where vi = 1% of viewport inline size (horizontal in LTR languages)
URL: https://utopia.fyi/type/calculator/
Inputs:
Output: Production-ready CSS custom properties using clamp()
Workflow:
:root selectorUtopia applies the same fluid logic to spacing, using multipliers derived from body font size (--step-0).
3XS: 0.25rem β 0.3125rem
2XS: 0.5rem β 0.625rem
XS: 0.75rem β 0.9375rem
S: 1rem β 1.25rem
M: 1.5rem β 1.875rem
L: 2rem β 2.5rem
XL: 3rem β 3.75rem
2XL: 4rem β 5rem
3XL: 6rem β 7.5rem
:root {
/* Single-value space tokens */
--space-3xs: clamp(0.25rem, 0.2321rem + 0.0893vi, 0.3125rem);
--space-2xs: clamp(0.5rem, 0.4643rem + 0.1786vi, 0.625rem);
--space-xs: clamp(0.75rem, 0.6964rem + 0.2679vi, 0.9375rem);
--space-s: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--space-m: clamp(1.5rem, 1.3929rem + 0.5357vi, 1.875rem);
--space-l: clamp(2rem, 1.8571rem + 0.7143vi, 2.5rem);
--space-xl: clamp(3rem, 2.7857rem + 1.0714vi, 3.75rem);
--space-2xl: clamp(4rem, 3.7143rem + 1.4286vi, 5rem);
--space-3xl: clamp(6rem, 5.5714rem + 2.1429vi, 7.5rem);
}
/* Usage */
.card {
padding: var(--space-m);
gap: var(--space-xs);
}
.section {
padding-block: var(--space-2xl);
}
.button {
padding: var(--space-2xs) var(--space-s);
}
For more dramatic fluid changes, Utopia provides space pairs that interpolate between two different space values.
:root {
/* S-M: 16px β 30px (more dramatic than either alone) */
--space-s-m: clamp(1rem, 0.7143rem + 1.4286vi, 1.875rem);
/* M-L: 24px β 40px */
--space-m-l: clamp(1.5rem, 1.1786rem + 1.6071vi, 2.5rem);
/* L-XL: 32px β 60px */
--space-l-xl: clamp(2rem, 1.4286rem + 2.8571vi, 3.75rem);
/* XL-2XL: 48px β 80px */
--space-xl-2xl: clamp(3rem, 2.1429rem + 4.2857vi, 5rem);
/* 2XL-3XL: 64px β 120px (very dramatic) */
--space-2xl-3xl: clamp(4rem, 2.8571rem + 5.7143vi, 7.5rem);
}
/* Usage - Components "breathe" dramatically */
.hero {
padding-block: var(--space-xl-2xl); /* 48px β 80px */
}
.section {
padding-block: var(--space-m-l); /* 24px β 40px */
gap: var(--space-s-m); /* 16px β 30px */
}
.card {
padding: var(--space-s-m);
gap: var(--space-xs); /* Subtle scaling */
}
.article {
/* Combine pairs for hierarchical spacing */
margin-block-start: var(--space-l-xl);
margin-block-end: var(--space-m-l);
}
When to Use Space Pairs:
URL: https://utopia.fyi/space/calculator/
Inputs:
Output: Individual space tokens + space pairs as CSS custom properties
Workflow:
:root alongside type scaleDesigners specify:
Developers implement:
.main-heading { font-size: var(--step-3); }
.section { padding-block: var(--space-l-xl); }
.card { padding: var(--space-m); }
Result: Both reference the same systematic scale, eliminating translation errors.
Traditional (breakpoint-heavy):
.heading {
font-size: 1.5rem;
}
@media (min-width: 640px) {
.heading { font-size: 2rem; }
}
@media (min-width: 1024px) {
.heading { font-size: 2.5rem; }
}
@media (min-width: 1280px) {
.heading { font-size: 3rem; }
}
Utopia (single declaration):
.heading {
font-size: var(--step-3); /* Scales automatically */
}
Advantages:
All spacing and typography decisions derive from the same mathematical foundation:
.component {
/* All values maintain proportional relationships */
font-size: var(--step-2); /* Scales proportionally */
line-height: 1.5; /* Unitless multiplier */
padding: var(--space-m); /* Scales with type */
gap: var(--space-xs); /* Maintains relationship */
margin-block: var(--space-s-m); /* Dramatic scaling */
}
.component__title {
font-size: var(--step-4); /* Larger than body */
margin-bottom: var(--space-2xs); /* Proportional to title size */
}
.component__meta {
font-size: var(--step--1); /* Smaller than body */
margin-top: var(--space-3xs); /* Proportional to meta size */
}
Key Principle: Every spacing and typography decision maintains intentional mathematical relationships rather than arbitrary pixel values.
:root {
/* Type Scale */
--step--2: clamp(0.6944rem, 0.6578rem + 0.1831vi, 0.8rem);
--step--1: clamp(0.8333rem, 0.7813rem + 0.2604vi, 1rem);
--step-0: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--step-1: clamp(1.2rem, 1.1036rem + 0.4821vi, 1.5625rem);
--step-2: clamp(1.44rem, 1.3113rem + 0.6435vi, 1.9531rem);
--step-3: clamp(1.728rem, 1.5585rem + 0.8476vi, 2.4414rem);
--step-4: clamp(2.0736rem, 1.8517rem + 1.1094vi, 3.0518rem);
--step-5: clamp(2.4883rem, 2.2008rem + 1.4372vi, 3.8147rem);
/* Space Scale - Individual Values */
--space-3xs: clamp(0.25rem, 0.2321rem + 0.0893vi, 0.3125rem);
--space-2xs: clamp(0.5rem, 0.4643rem + 0.1786vi, 0.625rem);
--space-xs: clamp(0.75rem, 0.6964rem + 0.2679vi, 0.9375rem);
--space-s: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--space-m: clamp(1.5rem, 1.3929rem + 0.5357vi, 1.875rem);
--space-l: clamp(2rem, 1.8571rem + 0.7143vi, 2.5rem);
--space-xl: clamp(3rem, 2.7857rem + 1.0714vi, 3.75rem);
--space-2xl: clamp(4rem, 3.7143rem + 1.4286vi, 5rem);
--space-3xl: clamp(6rem, 5.5714rem + 2.1429vi, 7.5rem);
/* Space Scale - Pairs */
--space-s-m: clamp(1rem, 0.7143rem + 1.4286vi, 1.875rem);
--space-m-l: clamp(1.5rem, 1.1786rem + 1.6071vi, 2.5rem);
--space-l-xl: clamp(2rem, 1.4286rem + 2.8571vi, 3.75rem);
--space-xl-2xl: clamp(3rem, 2.1429rem + 4.2857vi, 5rem);
--space-2xl-3xl: clamp(4rem, 2.8571rem + 5.7143vi, 7.5rem);
}
/* Typography */
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-3); }
body { font-size: var(--step-0); }
/* Spacing */
.hero { padding-block: var(--space-xl-2xl); }
.section { padding-block: var(--space-m-l); }
.card { padding: var(--space-m); gap: var(--space-s); }
.button { padding: var(--space-2xs) var(--space-s); }
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontSize: {
'step--2': 'var(--step--2)',
'step--1': 'var(--step--1)',
'step-0': 'var(--step-0)',
'step-1': 'var(--step-1)',
'step-2': 'var(--step-2)',
'step-3': 'var(--step-3)',
'step-4': 'var(--step-4)',
'step-5': 'var(--step-5)',
},
spacing: {
'3xs': 'var(--space-3xs)',
'2xs': 'var(--space-2xs)',
'xs': 'var(--space-xs)',
's': 'var(--space-s)',
'm': 'var(--space-m)',
'l': 'var(--space-l)',
'xl': 'var(--space-xl)',
'2xl': 'var(--space-2xl)',
'3xl': 'var(--space-3xl)',
's-m': 'var(--space-s-m)',
'm-l': 'var(--space-m-l)',
'l-xl': 'var(--space-l-xl)',
'xl-2xl': 'var(--space-xl-2xl)',
}
}
}
}
Usage:
<h1 class="text-step-5">Main Heading</h1>
<section class="py-xl-2xl">
<div class="card p-m gap-s">
<h2 class="text-step-3">Card Title</h2>
<p class="text-step-0">Body text</p>
</div>
</section>
utopia-container-queries skill for component-level fluidity.article {
font-size: var(--step-0);
line-height: 1.6;
max-width: 70ch;
}
.article h1 {
font-size: var(--step-4);
margin-block: var(--space-l-xl) var(--space-m);
}
.article h2 {
font-size: var(--step-3);
margin-block: var(--space-l) var(--space-s);
}
.article p + p {
margin-block-start: var(--space-m);
}
.card {
padding: var(--space-s-m);
gap: var(--space-s);
border-radius: 0.5rem;
}
.card__title {
font-size: var(--step-2);
margin-bottom: var(--space-2xs);
}
.card__meta {
font-size: var(--step--1);
color: var(--color-muted);
}
.hero {
padding-block: var(--space-2xl-3xl);
text-align: center;
}
.hero__title {
font-size: var(--step-5);
margin-bottom: var(--space-m);
}
.hero__subtitle {
font-size: var(--step-1);
margin-bottom: var(--space-l);
}
After implementing fluid scales:
utopia-grid-layout skill for Grid/Flexbox/Subgrid with fluid gapsutopia-container-queries skill for component-level container queries with container units