Variable-weight text fitting engine. Fill any rectangle with kinetic typography.
Renders as styled <div> elements instead of canvas — apply gradients, shadows, animations, Tailwind classes, anything.
Give it text and a width × height. It finds the optimal line breaks so every line stretches to fill the width.
Larger lines get heavier weight, smaller lines get lighter. Creates natural visual hierarchy with a single font.
Ships with React, Svelte, Vue, and Angular components. Or use the vanilla Canvas/DOM renderers directly.
DOM renderer outputs plain HTML — add className, Tailwind utilities, CSS transitions, shadows, gradients.
Works server-side with node-canvas or OffscreenCanvas. Generate OG images, thumbnails, PDFs.
~6 KB core engine, zero dependencies. Measurement cache for repeat calls. No layout thrashing.
npm install tightset
import { fit } from 'tightset'
import { render } from 'tightset/canvas'
const result = fit('Every Line Fills The Width', {
width: 800,
height: 500,
fontFamily: 'Inter',
})
await document.fonts.ready // wait for fonts before measuring
render(canvas, result, {
fontFamily: 'Inter',
color: '#ffffff',
background: '#0d0d0d',
})
fit(). Use await document.fonts.ready or trigger a re-render after fonts load. The framework components use canvas internally — ensure fonts are loaded before they mount.import { Tightset } from 'tightset/react'
<Tightset
text="Make It Tight"
width={800}
height={500}
fontFamily="Inter"
color="#ffffff"
background="#0d0d0d"
className="rounded-2xl shadow-xl"
/>
import Tightset from 'tightset/svelte'
<Tightset
text="Hello World"
width={800}
height={500}
fontFamily="Inter"
color="#ffffff"
background="#0d0d0d"
/>
<script setup>
import Tightset from 'tightset/vue'
</script>
<template>
<Tightset
text="Hello World"
:width="800"
:height="500"
fontFamily="Inter"
mode="html"
/>
</template>
import { TightsetComponent } from 'tightset/angular'
<tightset
text="Make It Tight"
[width]="800"
[height]="500"
fontFamily="Inter"
color="#ffffff"
background="#0d0d0d"
/>
tightset.component.ts into your project from node_modules/tightset/dist/angular/ rather than importing from tightset/angular directly, as Angular's AOT compiler cannot process raw .ts files from node_modules.import { fit } from 'tightset'
import { renderToHTML, getLineStyles } from 'tightset/dom'
const result = fit('Style Me', { width: 800, height: 400 })
// Option 1: HTML string with Tailwind classes
const html = renderToHTML(result, {
containerClass: 'bg-black rounded-2xl',
lineClass: 'tracking-tight drop-shadow-lg',
})
// Option 2: Style objects for JSX
const styles = getLineStyles(result, { fontFamily: 'Inter' })
result.lines.map((line, i) =>
<div style={styles[i]} className="drop-shadow-lg">{line}</div>
)
| Option | Default | Description |
|---|---|---|
| width | — | Box width in pixels (required) |
| height | — | Box height in pixels (required) |
| fontFamily | 'sans-serif' | Font family — must be loaded |
| padX | 60 | Horizontal padding |
| padY | 40 | Vertical padding |
| gap | 20 | Gap between lines |
| maxWeight | 900 | Heaviest font weight |
| spread | 150 | Weight range (heavy − light) |
| maxLines | 8 | Maximum line count |
| uppercase | true | Uppercase transform |
| Import | Contents |
|---|---|
| tightset | Core fit(), clearCache(), setMeasureContext() |
| tightset/canvas | draw(), render() for Canvas 2D |
| tightset/dom | renderToHTML(), renderToDOM(), getLineStyles() |
| tightset/react | <Tightset> React component |
| tightset/svelte | <Tightset> Svelte component |
| tightset/vue | <Tightset> Vue component |
| tightset/angular | <TightsetComponent> Angular standalone component |
import { createCanvas } from 'canvas'
import { setMeasureContext, fit } from 'tightset'
const canvas = createCanvas(1, 1)
setMeasureContext(canvas.getContext('2d'))
const result = fit('Server Side Text', {
width: 1200, height: 630, fontFamily: 'Inter'
})