Variable-weight text fitting engine. Fill any rectangle with kinetic typography.

Interactive demo

900
150
20
60
40
8
500 × 320
↘ Drag the corner to resize

DOM / Tailwind mode

Renders as styled <div> elements instead of canvas — apply gradients, shadows, animations, Tailwind classes, anything.

renderToDOM() — gradient text via CSS

What it does

Fills any rectangle

Give it text and a width × height. It finds the optimal line breaks so every line stretches to fill the width.

Variable font weight

Larger lines get heavier weight, smaller lines get lighter. Creates natural visual hierarchy with a single font.

Framework components

Ships with React, Svelte, Vue, and Angular components. Or use the vanilla Canvas/DOM renderers directly.

Tailwind-friendly

DOM renderer outputs plain HTML — add className, Tailwind utilities, CSS transitions, shadows, gradients.

SSR / Node.js

Works server-side with node-canvas or OffscreenCanvas. Generate OG images, thumbnails, PDFs.

Tiny & fast

~6 KB core engine, zero dependencies. Measurement cache for repeat calls. No layout thrashing.

Install

npm install tightset

Quick start

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',
})
Font loading: Canvas mode measures text via the Canvas 2D API, so fonts must be fully loaded before calling 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.

React

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"
/>

Svelte

import Tightset from 'tightset/svelte'

<Tightset
  text="Hello World"
  width={800}
  height={500}
  fontFamily="Inter"
  color="#ffffff"
  background="#0d0d0d"
/>

Vue

<script setup>
import Tightset from 'tightset/vue'
</script>

<template>
  <Tightset
    text="Hello World"
    :width="800"
    :height="500"
    fontFamily="Inter"
    mode="html"
  />
</template>

Angular

import { TightsetComponent } from 'tightset/angular'

<tightset
  text="Make It Tight"
  [width]="800"
  [height]="500"
  fontFamily="Inter"
  color="#ffffff"
  background="#0d0d0d"
/>
Note: The Angular component is shipped as TypeScript source. Copy 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.

Tailwind / DOM rendering

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>
)

Options

OptionDefaultDescription
widthBox width in pixels (required)
heightBox height in pixels (required)
fontFamily'sans-serif'Font family — must be loaded
padX60Horizontal padding
padY40Vertical padding
gap20Gap between lines
maxWeight900Heaviest font weight
spread150Weight range (heavy − light)
maxLines8Maximum line count
uppercasetrueUppercase transform

Import map

ImportContents
tightsetCore fit(), clearCache(), setMeasureContext()
tightset/canvasdraw(), render() for Canvas 2D
tightset/domrenderToHTML(), renderToDOM(), getLineStyles()
tightset/react<Tightset> React component
tightset/svelte<Tightset> Svelte component
tightset/vue<Tightset> Vue component
tightset/angular<TightsetComponent> Angular standalone component

Node.js / SSR

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'
})