GenomeJS
Utilities

fluidScale()

Generate a CSS clamp() expression that scales linearly between two pixel sizes.

fluidScale() creates a CSS clamp() expression that interpolates linearly between a minimum and maximum pixel value across a viewport range.

Despite its typography-focused source description, the returned expression can be used anywhere a CSS length is appropriate.

Import

import { fluidScale } from "@genomejs/core";

Signature

fluidScale(
  minPx: number,
  maxPx: number,
  minVw?: number,
  maxVw?: number,
): string

Parameters

ParameterTypeDefaultDescription
minPxnumberMinimum output size in pixels
maxPxnumberMaximum output size in pixels
minVwnumber320Viewport width where interpolation begins
maxVwnumber1440Viewport width where interpolation ends

Return value

string;

The returned value is a CSS clamp() expression.

Basic example

import { fluidScale } from "@genomejs/core";

const headingSize = fluidScale(32, 64);

console.log(headingSize);

The result has this shape:

clamp(
  32px,
  <intercept>px + <slope>vw,
  64px
)

Use it in CSS:

document.documentElement.style.setProperty("--heading-size", headingSize);
h1 {
  font-size: var(--heading-size);
}

Exact custom-range example

fluidScale(16, 32, 400, 1200);

Returns:

clamp(
  16px,
  8.0000px + 2.0000vw,
  32px
)

This exact output is covered by the Core tests.

Use in a Genome token

import { fluidScale, Genome } from "@genomejs/core";

const genome = new Genome({
  primitives: {
    headingMin: 32,
    headingMax: 64,
  },

  tokens: {
    headingSize: (dna) =>
      fluidScale(Number(dna.headingMin), Number(dna.headingMax)),
  },
});

GenomeJS expresses:

--g-heading-size: clamp(32px, ...px + ...vw, 64px);

Consume it normally:

.page-title {
  font-size: var(--g-heading-size);
}

Responsive spacing

The function can also create fluid spacing values:

const tokens = {
  pagePadding: () => fluidScale(16, 48, 320, 1280),
};
.page {
  padding-inline: var(--g-page-padding);
}

Responsive radius

const tokens = {
  panelRadius: () => fluidScale(12, 24, 320, 1440),
};
.panel {
  border-radius: var(--g-panel-radius);
}

Use fluid values only where continuous interpolation benefits the interface. Not every token needs to change with viewport width.

How the expression is calculated

The utility calculates a straight line:

value = slope × viewport + intercept

Where:

const slope = (maxPx - minPx) / (maxVw - minVw);

And:

const intercept = minPx - slope * minVw;

It then returns:

clamp(
  minPx,
  intercept + slope-in-vw,
  maxPx
)

The middle expression equals minPx at minVw and maxPx at maxVw, subject to the four-decimal string rounding used in the generated expression.

Defaults

These calls are equivalent:

fluidScale(16, 32);
fluidScale(16, 32, 320, 1440);

Input requirements

Use a viewport range where:

maxVw > minVw

And, for ordinary increasing scales:

maxPx >= minPx

The current implementation does not validate these relationships.

Equal viewport bounds

Avoid:

fluidScale(16, 32, 640, 640);

The calculation divides by:

maxVw - minVw;

Equal viewport bounds produce invalid numeric output.

Reversed ranges

This is accepted by JavaScript:

fluidScale(32, 16, 320, 1440);

But the resulting clamp() bounds may not behave as intended because CSS expects the first argument to act as the lower bound and the last as the upper bound.

Validate configuration before generation.

function createFluidScale(
  minPx: number,
  maxPx: number,
  minVw: number,
  maxVw: number,
): string {
  if (maxPx < minPx) {
    throw new Error("maxPx must be at least minPx.");
  }

  if (maxVw <= minVw) {
    throw new Error("maxVw must be greater than minVw.");
  }

  return fluidScale(minPx, maxPx, minVw, maxVw);
}

Pixel-only output

The utility accepts numeric pixel values and generates px and vw units.

It does not currently generate:

  • rem
  • em
  • Container-query units
  • Percentages
  • Custom unit combinations

For rem-based systems, either convert values before calling the function or implement a project-specific helper.

Error behavior

fluidScale() does not currently throw dedicated errors for:

  • Equal viewport bounds
  • Reversed viewport bounds
  • Reversed size bounds
  • Non-finite values
  • Negative values

Validate dynamic or user-provided input before calling it.

Notes

  • The default viewport range is 320px to 1440px.
  • The return value is a CSS clamp() string.
  • The preferred expression uses px + vw.
  • Numeric coefficients are formatted to four decimal places.
  • It can generate typography, spacing, or other CSS lengths.
  • Input relationships are not validated automatically.
  • It does not require a Genome instance.

On this page