Variable-weight text fitting engine. Fill any rectangle with kinetic typography.
Live demo →
Every line stretches to fill the width, with heavier weight on larger lines. Works with any variable-weight font. Ships with React, Svelte, Vue, Angular, vanilla Canvas, and DOM/Tailwind renderers.
npm install tightset
import { fit } from 'tightset'
import { render } from 'tightset/canvas'
await document.fonts.ready // always wait for fonts before canvas rendering
const result = fit('Every Line Fills The Width', {
width: 800,
height: 500,
fontFamily: 'Inter',
})
render(document.querySelector('canvas'), result, {
fontFamily: 'Inter',
color: '#ffffff',
background: '#0d0d0d',
})
Font loading: Canvas mode measures text via the Canvas 2D API, so the font must be fully loaded before calling
fit(). Wait fordocument.fonts.readyor use the CSS Font Loading API. The framework components (tightset/react,tightset/vue, etc.) use canvas internally — ensure your fonts are loaded before they mount, or trigger a re-render once fonts are ready.
| Import | What |
|---|---|
tightset |
Core engine: fit(), clearCache(), setMeasureContext() |
tightset/canvas |
Canvas 2D draw() and render() |
tightset/dom |
renderToHTML(), renderToDOM(), getLineStyles() — for Tailwind/CSS |
tightset/react |
<Tightset> React component |
tightset/svelte |
<Tightset> Svelte component |
tightset/vue |
<Tightset> Vue component |
tightset/angular |
<TightsetComponent> Angular standalone component |
import { Tightset } from 'tightset/react'
<Tightset
text="Make It Tight"
width={800}
height={500}
fontFamily="Inter"
color="#fff"
background="#000"
/>
<script>
import Tightset from 'tightset/svelte'
</script>
<Tightset
text="Hello World"
width={800}
height={500}
fontFamily="Inter"
color="#fff"
background="#000"
/>
<script setup>
import Tightset from 'tightset/vue'
</script>
<template>
<Tightset
text="Hello World"
:width="800"
:height="500"
fontFamily="Inter"
mode="html"
/>
</template>
Note: The Angular component is shipped as TypeScript source (not pre-compiled), so it cannot be consumed directly from
node_moduleswith AOT compilation. Copy the component into your project fromnode_modules/tightset/dist/angular/tightset.component.ts, or use the source from src/angular.
// Copy tightset.component.ts into your project, then:
import { TightsetComponent } from './tightset.component'
@Component({
standalone: true,
imports: [TightsetComponent],
template: `
<tightset
text="Make It Tight"
[width]="800"
[height]="500"
fontFamily="Inter"
color="#ffffff"
background="#0d0d0d"
/>
`,
})
export class MyComponent {}
import { fit } from 'tightset'
import { renderToHTML } from 'tightset/dom'
const result = fit('Style Me', { width: 800, height: 400, fontFamily: 'Inter' })
const html = renderToHTML(result, {
fontFamily: 'Inter',
containerClass: 'bg-black rounded-2xl',
lineClass: 'tracking-tight drop-shadow-lg',
})
Or get style objects for JSX:
import { getLineStyles } from 'tightset/dom'
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 (px, required) |
height |
— | Box height (px, required) |
fontFamily |
'sans-serif' |
Font family name |
padX |
60 |
Horizontal padding |
padY |
40 |
Vertical padding |
gap |
20 |
Line gap |
maxWeight |
900 |
Heaviest font weight |
spread |
150 |
Weight range (heavy − light) |
maxLines |
8 |
Max lines |
uppercase |
true |
Uppercase transform |
import { createCanvas } from 'canvas'
import { setMeasureContext, fit } from 'tightset'
setMeasureContext(createCanvas(1, 1).getContext('2d'))
const result = fit('Server Side', { width: 800, height: 400 })
Live demo → dmoptimal.github.io/tightset
Or run locally:
npx serve .
# Open http://localhost:3000/docs/
MIT