Skip to content
goodtabs UI
BrowseBrowse components

Fluid heading

A heading that scales along a line through two points, with slope and intercept printed.

npx shadcn@latest add https://ui.goodtabs.co/r/fluid-heading.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/fluid-heading

Type that scales

slope 0.01458, intercept 13.33px
clamp(1.125rem, 0.833rem + 1.458vw, 2rem)

18px at 320 to 32px at 1280, with the derived clamp() shown.

Large

Fluid between 390 and 1280

slope 0.02022, intercept 22.11px
clamp(1.875rem, 1.382rem + 2.022vw, 3rem)

30px at 390 to 48px at 1280 in a bordered box.

Usage

import { FluidHeading } from "@/components/ui/fluid-heading"

<FluidHeading minPx={30} maxPx={48} minVw={390} maxVw={1280}>
  Heading
</FluidHeading>

Props

PropTypeDefaultDescription
minPxnumber24Size in pixels at the small viewport.
maxPxnumber48Size in pixels at the large viewport.
minVwnumber320Small viewport width in pixels.
maxVwnumber1280Large viewport width in pixels.
showFormulabooleantrueShows slope, intercept and the clamp() value under the heading.

Source

fluid-heading/fluid-heading.tsx
import * as React from "react";
import { cn } from "cn";

type FluidHeadingProps = React.ComponentProps<"h2"> & {
  /** Size in px at the small viewport. */
  minPx?: number;
  /** Size in px at the large viewport. */
  maxPx?: number;
  /** Small viewport width in px. */
  minVw?: number;
  /** Large viewport width in px. */
  maxVw?: number;
  /** Prints slope, intercept and the clamp() under the heading. */
  showFormula?: boolean;
};

const round = (n: number, d = 3) => Number(n.toFixed(d));

/**
 * A heading whose size is a straight line through two points:
 * slope = (max - min) / (maxVw - minVw), intercept = min - slope * minVw.
 */
export function FluidHeading({
  minPx = 24,
  maxPx = 48,
  minVw = 320,
  maxVw = 1280,
  showFormula = true,
  className,
  children,
  ...props
}: FluidHeadingProps) {
  const slope = (maxPx - minPx) / (maxVw - minVw);
  const intercept = minPx - slope * minVw;
  const value = `clamp(${round(minPx / 16)}rem, ${round(intercept / 16)}rem + ${round(slope * 100)}vw, ${round(maxPx / 16)}rem)`;

  return (
    <div className="flex flex-col gap-2">
      <h2 className={cn("font-medium tracking-tight text-balance", className)} style={{ fontSize: value }} {...props}>
        {children}
      </h2>
      {showFormula ? (
        <p className="font-mono text-xs leading-relaxed text-muted-foreground tabular-nums">
          slope {round(slope, 5)}, intercept {round(intercept, 2)}px
          <br />
          {value}
        </p>
      ) : null}
    </div>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/fluid-heading/prompt.md, or the Copy AI prompt button above.

When to use

  • Page headings that should scale smoothly between two viewports
  • Docs that teach where a clamp() value comes from

When not to use

  • Body text: keep it fixed so zooming behaves
  • Pairs where the vw coefficient passes about 4, which fails zoom at 200 percent

Agent notes

  • Server component. The size is a clamp() computed at render, so there is no layout shift
  • Set showFormula to false in production and keep it for teaching pages
  • It renders an h2, pass className to restyle it

The maths

  • Slope = (maxPx - minPx) / (maxVw - minVw), so 14 over 960 is 0.01458.
  • Intercept = minPx - slope × minVw, so 18 - 0.01458 × 320 is 13.33px.
  • Size = clamp(min, intercept + slope × 100vw, max).