Bootstrap
Fluid font sizes in Bootstrap with CSS clamp()
How Bootstrap 5’s built-in responsive font sizes work, and how to swap them for clamp() in headings, display sizes and spacing.
Bootstrap 5 already makes large text smaller on small screens. It does that with a tool called RFS, not with clamp(). That works, but you do not control the start and end sizes, and every value snaps to its full size at one breakpoint.
This page shows what RFS does, and how to replace it with clamp() values from the clamp() generator. Everything here was tested against Bootstrap 5.3.8 and Dart Sass.
How Bootstrap’s RFS works
RFS stands for Responsive Font Sizes. Bootstrap turns it on by default with $enable-rfs: true and uses it for headings, display headings, the .fs-* utilities and a few components like legend. The default h3 compiles to:
h3, .h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3, .h3 {
font-size: 1.75rem;
}
}- Below 1200px, the size is a
calc()that grows with the viewport. - From 1200px up, a media query sets the fixed size from
$h3-font-size. - Sizes of
1.25remor less, the default$rfs-base-value, are never scaled. That is whyh5andh6stay fixed. - How much smaller the text gets on a phone depends on
$rfs-factor, not on a size you pick.
With clamp() you choose both sizes and both screen widths yourself, and you get one declaration instead of two.
Replacing heading sizes with clamp()
Set the variables before you import Bootstrap’s variables file. Bootstrap declares its defaults with !default, so your values win.
// custom.scss
@import 'bootstrap/scss/functions';
// 36px to 64px, 30px to 48px, … between 360px and 1440px
$h1-font-size: clamp(2.25rem, 1.667rem + 2.593vw, 4rem);
$h2-font-size: clamp(1.875rem, 1.5rem + 1.667vw, 3rem);
$h3-font-size: clamp(1.5rem, 1.25rem + 1.111vw, 2.25rem);
$h4-font-size: clamp(1.25rem, 1.083rem + 0.741vw, 1.75rem);
$h5-font-size: clamp(1.125rem, 1.042rem + 0.37vw, 1.375rem);
$h6-font-size: clamp(1rem, 0.958rem + 0.185vw, 1.125rem);
$display-font-sizes: (
1: clamp(3rem, 2.333rem + 2.963vw, 5rem),
2: clamp(2.75rem, 2.167rem + 2.593vw, 4.5rem),
3: clamp(2.5rem, 2rem + 2.222vw, 4rem),
4: clamp(2.25rem, 1.833rem + 1.852vw, 3.5rem),
5: clamp(2rem, 1.667rem + 1.481vw, 3rem),
6: clamp(1.75rem, 1.5rem + 1.111vw, 2.5rem),
);
@import 'bootstrap/scss/bootstrap';That compiles to a single rule per heading, with no media query:
h1, .h1 {
font-size: clamp(2.25rem, 1.667rem + 2.593vw, 4rem);
}
.display-1 {
font-size: clamp(3rem, 2.333rem + 2.963vw, 5rem);
/* … font-weight, line-height */
}
.fs-1 {
font-size: clamp(2.25rem, 1.667rem + 2.593vw, 4rem) !important;
}$font-sizes, the map behind.fs-1to.fs-6, is built from the heading variables, so the utilities follow automatically.- Overriding
$display-font-sizesreplaces the whole map. List all six steps, or the ones you leave out disappear. - Recent Dart Sass versions warn that
@importis deprecated. That is how Bootstrap 5 is set up, and the warnings are safe to silence with--silence-deprecation=import.
Do you need $enable-rfs: false?
No. RFS only rescales numbers in px or rem. For anything else, including a clamp(), it prints the value unchanged and skips the media query. You can leave RFS on, and the sizes you did not replace, like the legend element, keep their RFS behaviour.
Set $enable-rfs: false only if you want every size you did not replace to be fixed. Then no calc() values or 1200px media queries are left from RFS at all.
Fluid spacing with $spacers
Bootstrap’s spacing utilities come from the $spacers map. Add a clamp() value under a new key. Because you redefine the map, repeat the default steps too.
@import 'bootstrap/scss/functions';
$spacer: 1rem;
$spacers: (
0: 0,
1: $spacer * 0.25,
2: $spacer * 0.5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
// 16px to 48px between 360px and 1440px
section: clamp(1rem, 0.333rem + 2.963vw, 3rem),
);
@import 'bootstrap/scss/bootstrap';That gives you p-section, py-section, mt-section, gap-section and responsive versions like py-lg-section. Bootstrap also uses $spacers for grid gutters, so g-section sets a fluid gutter on a row.
<section class="py-section">
<div class="row g-section">…</div>
</section>Negative margins
If you turn on $enable-negative-margins, Bootstrap tries to negate every spacer, and Sass stops with Undefined operation "-clamp(…)". Define $negative-spacers yourself, next to $spacers and before you import Bootstrap, and wrap the fluid value in calc(-1 * …):
$negative-spacers: (
n1: -0.25rem,
n2: -0.5rem,
n3: -1rem,
n4: -1.5rem,
n5: -3rem,
nsection: calc(-1 * clamp(1rem, 0.333rem + 2.963vw, 3rem)),
);That gives you classes like mt-nsection, with the fluid value negated.
Without Sass: plain CSS overrides
If you load Bootstrap’s compiled CSS from a CDN, override the sizes in your own stylesheet, loaded after Bootstrap:
:root {
--text-h1: clamp(2.25rem, 1.667rem + 2.593vw, 4rem);
--text-h2: clamp(1.875rem, 1.5rem + 1.667vw, 3rem);
--space-section: clamp(1rem, 0.333rem + 2.963vw, 3rem);
}
h1, .h1 { font-size: var(--text-h1); }
h2, .h2 { font-size: var(--text-h2); }
.section-y {
padding-block: var(--space-section);
}A media query does not add specificity. Bootstrap’s 1200px rule for h1 has the same specificity as yours, so the rule that comes last wins. Put your file after Bootstrap and the clamp() applies at every width. The .fs-* utilities use !important, so for those a new class like .section-y is cleaner than an override.
Tips
- Keep one viewport range for every value, so headings and spacing grow in step. Set it once in the generator.
- Keep Bootstrap’s breakpoints, like
col-md-6, for layout. clamp() is for sizes, not for changing the number of columns. - Writing lots of values? A Sass fluid() function can calculate them for you at compile time.
- New to clamp()? The guide explains how to read a value.
Bootstrap clamp() FAQ
Does Bootstrap 5 use clamp() for font sizes?
No. Bootstrap 5 uses RFS, which writes a calc() value like calc(1.375rem + 1.5vw) and a @media (min-width: 1200px) rule that switches to the fixed size. You can replace those values with clamp() by overriding the Sass variables.
Do I need to set $enable-rfs to false to use clamp()?
No. RFS only rescales plain px and rem numbers. Any other value, including a clamp(), is printed as is without a media query. Keep $enable-rfs on if you want RFS for the sizes you did not replace, or turn it off to make every other size fixed.
How do I make Bootstrap headings fluid with clamp()?
Set $h1-font-size to $h6-font-size to clamp() values before you import Bootstrap’s variables. The h1 to h6 elements, the .h1 to .h6 classes and the .fs-1 to .fs-6 utilities all pick them up.
Can I add a clamp() value to Bootstrap spacers?
Yes. Add a key to the $spacers map, for example section: clamp(1rem, 0.333rem + 2.963vw, 3rem). Bootstrap then generates p-section, my-section, gap-section and g-section. If you enable negative margins, also set $negative-spacers yourself.
How do I use clamp() with Bootstrap from a CDN?
Without Sass, add your own CSS after Bootstrap’s stylesheet, for example h1, .h1 { font-size: clamp(2.25rem, 1.667rem + 2.593vw, 4rem); }. Media queries do not add specificity, so your later rule wins at every screen size.