A lightweight, polymorphic React component that applies fluid typography using CSS clamp(). Font size scales smoothly between a minimum and maximum value as the viewport grows — no media queries needed.
There is no npm package — just copy the file directly into your project.
- Copy
FluidText.tsxanywhere in your source tree, e.g.src/components/FluidText.tsx - Import and use it:
import FluidText from './components/FluidText'Prefer plain JavaScript? Copy FluidText.jsx instead — same behaviour, no TypeScript.
Requirements:
- React 18 or 19
- TypeScript is required for the
.tsxversion (generics power the polymorphic type safety); the.jsxversion works in any JS project
That's it. No dependencies beyond React itself.
Under the hood, FluidText generates a single clamp() expression:
clamp(<min>px, calc(1vw * (<max> * 100 / <viewportWidth>)), <max>px)
- Below the breakpoint the font stays at
minFontSize. - Inside the range it scales linearly with the viewport.
- Above
viewportWidthit caps atmaxFontSize.
import FluidText from './component/FluidText'
// Renders a <p> that scales from 16px → 32px across a 1440px viewport
<FluidText minFontSize={16} maxFontSize={32}>
Hello, fluid world.
</FluidText>FluidText is polymorphic — the as prop controls which HTML element is rendered.
TypeScript automatically infers the correct props for each element.
// Headings
<FluidText as="h1" minFontSize={32} maxFontSize={72}>
Page title
</FluidText>
<FluidText as="h2" minFontSize={24} maxFontSize={48}>
Section heading
</FluidText>
// Inline elements
<FluidText as="span" minFontSize={14} maxFontSize={18}>
Inline fluid text
</FluidText>
// Anchor — href is type-safe because as="a"
<FluidText as="a" href="https://example.com" minFontSize={14} maxFontSize={20}>
A fluid link
</FluidText>
// Button — type and disabled are inferred correctly
<FluidText as="button" type="submit" disabled minFontSize={14} maxFontSize={18}>
Submit
</FluidText>
// Label — htmlFor is available
<FluidText as="label" htmlFor="email" minFontSize={12} maxFontSize={16}>
Email address
</FluidText>The default viewport width is 1440px. Override it with viewportWidth:
// Scale relative to a 1280px design baseline
<FluidText minFontSize={16} maxFontSize={28} viewportWidth={1280}>
Scaled to 1280px baseline
</FluidText>
// Tighter range for a mobile-first layout
<FluidText minFontSize={14} maxFontSize={20} viewportWidth={768}>
Mobile-first fluid text
</FluidText>Omit minFontSize / maxFontSize entirely and the component becomes a simple
polymorphic wrapper — useful when you want the fluid-text class or the as
convenience without any size computation.
<FluidText as="p" className="body-copy">
Static size, polymorphic element.
</FluidText>The component always adds a fluid-text class so you can target it globally in CSS.
Any className or style you pass is merged in.
// Extra class
<FluidText as="h1" minFontSize={32} maxFontSize={64} className="hero-title">
Big title
</FluidText>
// Inline style — your values are spread after the computed fontSize,
// so they take precedence (e.g. to pin a specific size in one context)
<FluidText
minFontSize={16}
maxFontSize={32}
style={{ color: 'rebeccapurple', letterSpacing: '0.02em' }}
>
Styled fluid text
</FluidText>export function ArticlePage() {
return (
<article>
<FluidText as="h1" minFontSize={32} maxFontSize={72} className="article-title">
The Future of Web Typography
</FluidText>
<FluidText as="p" minFontSize={16} maxFontSize={20} className="article-lead">
An introduction to fluid type and why it matters.
</FluidText>
<FluidText as="h2" minFontSize={22} maxFontSize={40}>
Why CSS clamp?
</FluidText>
<FluidText as="p" minFontSize={15} maxFontSize={18}>
The <code>clamp()</code> function lets a value scale between a minimum
and maximum, relative to a viewport unit — with zero JavaScript.
</FluidText>
<FluidText as="a" href="/docs" minFontSize={14} maxFontSize={16}>
Read the full docs →
</FluidText>
</article>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
as |
ElementType |
'p' |
HTML element to render |
minFontSize |
number |
— | Minimum font size in px |
maxFontSize |
number |
— | Maximum font size in px |
viewportWidth |
number |
1440 |
Viewport width (px) at which maxFontSize is reached |
className |
string |
— | Merged with the automatic fluid-text class |
style |
CSSProperties |
— | Merged with the computed fontSize (your values win) |
...props |
element props | — | All other props forwarded to the underlying element |
Fluid sizing is applied only when both minFontSize and maxFontSize are provided.
Got an idea to improve FluidText? PRs are very welcome.
Here are some directions the component could grow — pick one up or bring your own:
minViewport/maxViewportprops — let callers control the breakpoint range rather than deriving it fromviewportWidthalone- Line-height scaling — fluid
line-heightalongside fluid font size forwardRefsupport — expose a ref to the underlying element- CSS custom properties output — optionally emit
--fluid-min,--fluid-maxvariables for easier theming - Storybook stories — visual documentation and an interactive prop explorer
- More test coverage — edge cases, zero values, negative guard rails
To open a pull request:
- Fork the repo and create a branch:
git checkout -b feat/your-idea - Make your changes and add tests for any new behaviour
- Run
npm testandnpm run lint— both must pass - Open a PR with a short description of what you changed and why
All contributions, big or small, are appreciated.