How to make table columns as small as possible?

Dear all,

Yesterday I’ve heard about typst the first time, and after having looked into it for an hour or so it was clear that this is what I’ve been after for a very long time. I’m quite excited about it!

I’ve spent yesterday and today on learning as much as possible about it. Now I am facing a little problem that I’m not able to solve myself. Please consider the following table definition:

#table(
  columns: (auto, auto, 1fr, auto, auto),
  align: horizon,
  table.header(
    repeat: true
  )[Order line][Qty.][Description / Service IDs][Monthly fee][Setup fee]
)[a][b][c][d][e]


This produces the following result:
Screenshot 2026-08-08 195023

Now I would like to make each column as small as possible, except the middle column, which should be as wide as possible. Therefore, I would like the text in each cell in each column (except the middle column) to be line-wrapped wherever a wrap is possible. As an example, in the first header cell, there should be a line break after the word “Order” so that the word “line” is put in a new line.

The obvious solution would be to insert an explicit line break between the two words (that is, [Order\ line] instead of [Order line]).

Unfortunately, this is not a solution to the problem, because there is a second requirement:

If there is a text somewhere in the first column that cannot be line-wrapped (for example, because it does not contain a word separator like a space character), and if that text takes more horizontal space than the text in the first header cell would take, the text in the first header cell should remain on one line.

Probably it would be possible to achieve the desired behavior by a script which is very complex. Before taking that effort, I would like to know whether there is easy way that I have missed.

Thanks in advance for any ideas!

After three hours, finally the perfect script:

#let string(content) = {
  if content == none {""}
  else if type(content) == str {content}
  else if type(content) == array {content.map(string).join(", ")}
  else if content.has("text") {content.text}
  else if content.has("children") {
    if content.children.len() == 0 {""}
    else {content.children.map(string).join("")}}
  else if content.has("child") {string(content.child)}
  else if content.has("body") {string(string(content.body))}
  else if content == [] {""}
  else if content == [ ] {" "}
  else if content.func() == ref {"_ref_"}
  else {let offending = content; ""}}

#let wrap(width, words) = {
  let new = ""
  let len = words.len()
  let i = 1
  while i <= len {
    for chr in words {
      new = new + chr
      if calc.rem(i, width) == 0 {
        new = new + "\n"}
      i = i + 1}}
  return rect(
    inset:(x:0.5em, y:0.5em), 
    stroke:none,
    new)}

#show table.cell: it => {
  let b = string(it)
  let x = it.x
  if it.y > 0 {
    if x == 0 {wrap(6, b)}
    else if x == 1 {wrap(3, b)}
    else if x == 3 {wrap(8, b)}
    else if x == 4 {wrap(6, b)}
    else {it}}
  else {it}}

#table(
  columns: (4em, 2.5em, 1fr, 6em, 4em),
  align: horizon,
  table.header(
    repeat: true
  )[Order\ line][Qty.][Description / Service IDs][Monthly fee][Setup fee]
)[a][b][c][d][e][#lorem(24)][s98e7f89se7f98se79f87ws98g7s987gd9]

All u need to edit, is the wrap(width,) and table(columns:(em,)),

$ 1 width = 1.2~1.5 em $
2 Likes

Thank you very much for that pearl!

I am currently trying to understand your code, and have two questions for now:

1.
In the line

else if content.has("body") {string(string(content.body))}

why is there a recursive call to string()? Technically, the result should be exactly the same as if there was

else if content.has("body") {string(content.body)}

I suspect that I am missing something important here. Could you please shortly explain?

2.
There is the line

else if content.func() == ref {"_ref_"}

Obviously, the condition checks if the content is a reference, and if yes, returns the literal string _ref_. In the resulting file, this would lead to the string ref in italics. That is, the reference would be replaced by the string ref in italics.

Again, I am obviously missing something. Could you please shortly explain?