Skip to content

Instantly share code, notes, and snippets.

@KonradJam
Created March 10, 2025 09:29
Show Gist options
  • Select an option

  • Save KonradJam/7a2c382415197da0e46dcd4f38026958 to your computer and use it in GitHub Desktop.

Select an option

Save KonradJam/7a2c382415197da0e46dcd4f38026958 to your computer and use it in GitHub Desktop.
A fluid typography system built with Sass, allowing unlimited breakpoints and smooth scaling using clamp()

Fluid Typography with Sass

This project provides a simple way to implement fluid typography in Sass using mixins and functions. It enables responsive font sizing that adjusts based on the viewport width while maintaining a smooth transition between different breakpoints.

Features

  • Uses clamp() for smooth scaling between min and max font sizes.
  • Supports multiple predefined text styles.
  • Automatically converts pixel values to rem for better scalability.
  • Customizable via SCSS maps.

Installation

Simply copy the scss file into your project and import it into your main Sass file:

@import 'fluid-typography';

Usage

To apply predefined typography styles, use the provided mixins in your SCSS:

.title {
  @include fluid-typography($text-preset-1);
}

.subtitle {
  @include fluid-typography($text-preset-2);
}

Custom Font Scaling

You can define your own font presets using the same structure:

$text-preset-2: (
  font-size: (
    font-size-1: (fs: 2.5rem, vw: 375px),
    font-size-2: (fs: 3rem, vw: 768px),
    font-size-3: (fs: 3.75rem, vw: 1024px)
  ),
  styles: (
    color: black,
    font-family: ("Open Sans", sans-serif),
    font-weight: 900, 
    letter-spacing: 0px, 
    line-height: 110%
  )
);

.subtitle {
  @include fluid-typography($text-preset-2);
}

How It Works

The fluid-typography mixin extracts font-size values from a provided preset and generates responsive styles using clamp(). It ensures smooth transitions between defined breakpoints without the need for multiple media queries.

Functions

  • px-to-rem($value-in-px): Converts pixel values to rem.
  • is-px($value): Checks if a value is in pixels and converts it if necessary.
  • clamp-size($size-min, $size-max, $viewport-min, $viewport-max): Creates a clamp() function for font scaling.

Example Output

For the .subtitle class, the following CSS is generated:

.subtitle {
  color: #aaa;
  font-family: "Open Sans", sans-serif;
  font-weight: 900;
  letter-spacing: 4px;
  line-height: 150%;
  text-transform: uppercase;
  font-size: clamp(0.75rem, calc(0.6307251908rem + 0.5089058524vi), 0.875rem);
}
@media (min-width: 48rem) {
  .subtitle {
    font-size: clamp(0.875rem, calc(0.5rem + 0.78125vi), 1rem);
  }
}

License

This project is open-source and free to use.

@use "sass:map";
@use "sass:list";
@use "sass:math";
@function px-to-rem($value-in-px, $font-size-browser: 16px) {
@return ($value-in-px / $font-size-browser) * 1rem;
}
@function is-px($value) {
@if (list.index("px", math.unit($value))) {
@return px-to-rem($value);
} @else {
@return $value;
}
}
@function clamp-size($size-min, $size-max, $viewport-min, $viewport-max) {
$size-min: is-px($size-min);
$size-max: is-px($size-max);
$viewport-min: is-px($viewport-min);
$viewport-max: is-px($viewport-max);
$change: ($size-max - $size-min) / ($viewport-max - $viewport-min);
$a: $size-max - ($viewport-max * $change);
$b: 100vi * $change;
@return clamp(#{$size-min}, calc(#{$a} + #{$b}), #{$size-max});
}
@mixin fluid-typography($preset) {
@each $style-name, $style-value in map.get($preset, styles) {
#{$style-name}: #{$style-value};
}
$font-sizes: map.get($preset, font-size);
@if (list.length($font-sizes) == 1) {
font-size: map.get(map.get($font-sizes, font-size-1), fs);
} @else {
@for $i from 2 through list.length($font-sizes) {
$font-size-min: map.get(map.get($font-sizes, font-size-#{$i - 1}), fs);
$font-size-max: map.get(map.get($font-sizes, font-size-#{$i}), fs);
$viewport-min: map.get(map.get($font-sizes, font-size-#{$i - 1}), vw);
$viewport-max: map.get(map.get($font-sizes, font-size-#{$i}), vw);
@if ($i > 2) {
@media (min-width: $viewport-min) {
font-size: clamp-size($font-size-min, $font-size-max, $viewport-min, $viewport-max);
}
} @else {
font-size: clamp-size($font-size-min, $font-size-max, $viewport-min, $viewport-max);
}
}
}
}
@import "fluid-typography";
$color-primary: #aaa;
$color-secondary: black;
$viewport-mobile: 23.4375rem; // 375px
$viewport-tablet: 768px;
$viewport-desktop: 64rem; // 1024px
$viewport-large: 1440px;
$font-family-primary: "Open Sans", sans-serif;
$text-preset-2: (
font-size: (
font-size-1: (fs: 2.5rem, vw: $viewport-mobile),
font-size-2: (fs: 3rem, vw: $viewport-tablet),
font-size-3: (fs: 3.75rem, vw: $viewport-desktop)
),
styles: (
color: $color-primary,
font-family: $font-family-primary,
font-weight: 900,
letter-spacing: 0px,
line-height: 110%
)
);
.subtitle {
@include fluid-typography(text-preset-2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment