Fit text into fixed Typst frames by adjusting the text size.
Framefit is useful when the frame size is fixed but the text is variable: labels, badges, cards, flyers, product sheets, certificates, data-driven templates, or any layout where user-provided copy must stay inside a known box.
The package measures the rendered text and chooses a font-size percentage that fits the available width and height. It can grow short text, shrink long text, cap the allowed growth, or keep text within a maximum number of lines.
Finite maximum:
No configured maximum:
Shrink to minimum:
Maximum lines:
Font and line spacing sensitivity:
- Fit text to a fixed
widthandheight. - Grow short text up to the largest fitting size.
- Shrink overflowing text down to a configured minimum.
- Use
max: noneormax: -1for no configured upper limit. - Use a finite
max, for examplemax: 140%, to cap growth. - Use
max-lines, for examplemax-lines: 3, to keep text within a measured line count. - Use
only-if-overflow: trueto keep normal text unchanged unless it would overflow.
After publishing to Typst Universe:
#import "@preview/framefit:0.1.0": framefit, fit-copyFor local development from this repository:
#import "lib.typ": framefit, fit-copyCreate a fitted frame directly:
#import "@preview/framefit:0.1.0": framefit
#framefit(
width: 70mm,
height: 24mm,
min: 70%,
max: none,
inset: 6pt,
stroke: 0.5pt,
)[
This text grows or shrinks until it fits the frame.
]Use the lower-level helper inside an existing frame:
#import "@preview/framefit:0.1.0": fit-copy
#block(width: 70mm, height: 24mm, stroke: 0.5pt, inset: 6pt)[
#fit-copy(min: 70%)[
This text uses the surrounding block as the frame.
]
]max: none is the default. Framefit grows the text until it first overflows,
then backs off to the largest fitting size.
#framefit(width: 60mm, height: 18mm, min: 70%, max: none)[
Short headline
]max: -1 is accepted as an alias for max: none.
Use a percentage max when text should grow, but not beyond a design limit.
#framefit(width: 60mm, height: 18mm, min: 70%, max: 180%)[
Short headline
]Use only-if-overflow: true to keep text at 100% when it already fits.
#framefit(
width: 70mm,
height: 24mm,
min: 70%,
max: 130%,
only-if-overflow: true,
)[
Text stays at normal size unless it would overflow.
]Use max-lines when the text should stay within a fixed number of measured
lines as well as the physical frame.
#framefit(
width: 70mm,
height: 30mm,
min: 60%,
max: none,
max-lines: 3,
)[
This text is fitted while staying within three measured lines.
]Line spacing is configured with Typst's normal paragraph setting:
#set par(leading: 4pt)
#framefit(width: 70mm, height: 30mm, max-lines: 3)[
This text is fitted using the active paragraph leading.
]#framefit(
width: auto,
height: auto,
min: 70%,
max: none,
max-lines: none,
steps: 24,
inset: 0pt,
stroke: none,
fill: none,
radius: 0pt,
only-if-overflow: false,
body,
)Creates a block frame and fits body inside it.
Key arguments:
width,height: frame dimensions passed toblock.min: smallest allowed text size as a percentage of the current text size.max: largest allowed text size as a percentage, ornone/-1for no configured maximum.max-lines: maximum measured line count, ornone.steps: binary-search iterations. The default is usually enough.inset,stroke,fill,radius: forwarded to the createdblock.only-if-overflow: iftrue, text that already fits stays at100%.
#fit-copy(
min: 70%,
max: none,
max-lines: none,
steps: 24,
only-if-overflow: false,
body,
)Fits body to the size of the surrounding layout container. Use this when you
already have a custom block or another layout container and only need the
text-fitting behavior.
Framefit uses Typst's layout measurement:
- Measure the content at a candidate font-size percentage.
- Check whether the measured width and height fit the available frame.
- If
max-linesis set, measure an equivalent line-count sample in the current text style and compare heights. - Use binary search to choose the largest fitting percentage.
Conceptually, max-lines is the maximum allowed text height for the requested
line count. Framefit lets Typst calculate that height instead of multiplying
manually, because the real line box depends on font metrics, text size,
top/bottom edges, and par(leading:).
The examples/ folder contains rendered documents for the main cases:
grows-to-max.typ: finite maximum percentage.no-maximum.typ: calculated maximum withmax: none.shrinks-to-min.typ: tight frame that reaches the minimum size.max-lines.typ: line-count limiting.max-lines-styles.typ: different fonts and paragraph leading.demo.typ: combined overview.
Each example includes a visible "Settings used" block before each result.
If text still does not fit at min, compilation fails with a clear error:
framefit: content does not fit at the minimum size.
Make the frame larger, reduce the content, or lower `min`.
This is intentional. It prevents silent clipping or hidden layout failures.
- Designed for paged output: PDF, PNG, and SVG.
- The fitting calculation uses Typst layout measurement, so unusual content may need manual checking.
max-linesis intended for ordinary text. It is less exact for content that changes style inside the body, includes non-text elements, uses manual line breaks, or relies on hyphenation.- The MVP focuses on text content. Other content can work, but is not the primary target.
Compile all examples:
for file in examples/*.typ; do
[ "$file" = "examples/_helpers.typ" ] && continue
docker run --rm -v "$PWD":/work -w /work ghcr.io/typst/typst:latest \
compile --root /work "$file" "${file%.typ}.pdf"
doneRun the compile checks:
for file in tests/*.typ; do
[ "$file" = "tests/impossible.typ" ] && continue
docker run --rm -v "$PWD":/work -w /work ghcr.io/typst/typst:latest \
compile --root /work "$file" "${file%.typ}.svg"
donetests/impossible.typ is expected to fail because it verifies the minimum-size
overflow error.




