How can I make a figure caption left-aligned or centered, matching the alignment to the number of lines in the caption

I want my figure captions to be centered if they occupy a single line, or left-aligned (or justified) if they span more than one line. I currently have a caption style that I want to preserve


show figure.caption: it => [
    #strong[#it.supplement
    #context it.counter.display(it.numbering) 
    #it.separator]
    #it.body
 ]

Would #show figure.caption: set align(left) suffice? It aligns the caption to the left of the figure block (the green rectangles in the second screenshot).

#show figure.caption: set align(left)

#show figure.caption: it => {
  strong({
    it.supplement
    [ ]
    context it.counter.display(it.numbering)
    it.separator
  })
  [ ]
  it.body
}

#figure(rect[Image], caption: [Caption])
#figure(rect[Image], caption: lorem(20))

Besides, newlines in markup mode ([…]) become spaces, and newlines in code mode ({…}) are ignored. You can use code mode to handle the spaces right.

See Create a dedicated section on dangers of using square brackets carelessly (multi-line) Ā· Issue #6844 Ā· typst/typst Ā· GitHub for more details about the newlines.

I also want it to be centered in case the figure is large but the description is short; is there any way to achieve this dynamically without having to change:

#show figure.caption: set align(center)

And changed it back when the caption is large?

Oh, I missed this case.

How about this? I put the caption into a block. The whole block is centered, and texts inside the block are aligned to the left.

#set page(height: auto, width: 240pt, margin: 1em)

#show figure.caption: it => block({
  set align(left)
  strong({
    it.supplement
    [ ]
    context it.counter.display(it.numbering)
    it.separator
  })
  [ ]
  it.body
})

#figure(rect[Image], caption: [Caption])
#figure(rect[A super large Image, very large], caption: [Caption])
#figure(rect[Image], caption: lorem(20))

Very creative :slightly_smiling_face:. Yes, this solves the problem; I think it gives a better effect. Many books use this style.

This is a great solution!
Is it possible to restrict the block to only a certain width while retaining the rest? Specifically, I’d love to insert shrink the width of the block statement as follows, but doing this leads to loss of the centering of the block in case of one-liners.

#set page(height: auto, width: 240pt, margin: 1em)

#show figure.caption: it => block(
width: 95%, 
{
  set align(left)
  strong({
    it.supplement
    [ ]
    context it.counter.display(it.numbering)
    it.separator
  })
  [ ]
  it.body
})

#figure(rect[Image], caption: [Caption])
#figure(rect[A super large Image, very large], caption: [Caption])
#figure(rect[Image], caption: lorem(20))

Thank you!

How about using two blocks?

#set page(height: auto, width: 240pt, margin: 1em)

#show figure.caption: it => block(
  width: 70%, // To make it more visible, I change it from 95% to 70%
  block({
    set align(left)
    strong({
      it.supplement
      [ ]
      context it.counter.display(it.numbering)
      it.separator
    })
    [ ]
    it.body
  }),
)

#block(width: 100%, stroke: green, inset: 1em)[
  For reference, the width of this block is #100%.
]

#figure(rect[Image], caption: [Caption])
#figure(rect[A super large Image, very large], caption: [Caption])
#figure(rect[Image], caption: lorem(20))

Perfect, thanks so much! :slight_smile: