How to style the terms right aligned in a terms list?

(Beginning Typst user here)

So I tried my hand at making the terms in a terms list right aligned. This is what I ended up:

#set par(
  justify: true, 
  //spacing: 0.65em,
  first-line-indent: 1.2em,
)

#set terms(
  separator: [:#h(.65em)],
  hanging-indent: 6em,
  indent: 4em, 
)

#let rightalignedterms(body) = {
    show terms.item: it => context{
      let label = strong[#it.term#terms.separator]
      let width = measure(label).width.to-absolute()
      let indent = terms.hanging-indent.to-absolute()
      let lspace = 0pt
      let rspace = 0pt
      if width < indent {
        lspace = indent - width
      } else if width > indent {
        rspace = width - indent
      }
      pad(left: terms.indent, { 
        place(float: false, [#h(lspace)#label])
        pad(left: indent, [#h(rspace)#it.description])
        //if terms.tight { linebreak() } else { parbreak() }
      })
    }
  body
}

#let termtest = [
  / term1: test #lorem(60)
  / term2: test test
  
   #lorem(90)  
  / term3 #lorem(5): #lorem(60)
]

#termtest
#rightalignedterms[#termtest]

As you can see, it does not respect the inline vs block appearance of the terms list. I guess it is because of the multiple pads forcing a block level layout?

The place(float: false) with spacing underneath is really ugly, but if I use float: true, the terms end up grouped together at the top of the page. I would prefer to make new paragraphs for each term with a hanging indent, but my major problem with that is it.description itself can contain multiple paragrahps that will get the same hanging indent.

(I know that this solution won’t work with multiline terms)

Anyway, looking forward to commentary on this.

I’m uncertain whether this achieves exactly what you were aiming for, as I could use some screenshots.

Regardless, here’s my couple of attempts, both reimplement terms in similar ways, having their own pros and cons, but neither require context:

  1. Output as a block:

    • Pros: The structure shouldn’t be much different from the original.
    • Cons: Term items’ term is aligned to the bottom.
    #show terms: it => {	
    	let new-terms = it.children.map(item =>
    		strong(item.term + it.separator) +
    		box(
    			width: 1fr,
    			stroke: blue,
    			align(
    				right,
    				par(
    					hanging-indent: it.hanging-indent,
    					item.description,
    				),
    			),
    		)
    	)
    
    	for term in new-terms {
    		block(
    			stroke: red,
    			inset: (left: it.indent),
    			term,
    		)
    	}
    }
    

  2. Output as a grid:

    • Pros: Should be as expected.
    • Cons: Different behaviour, because of how grid cells break.
    #show terms: it => grid(
    	columns: (auto, 1fr),
    	row-gutter: 1em,
    	align: right,
    	stroke: blue,
    	..it.children.map(item => (
    			pad(left: it.indent, strong(item.term + it.separator)),
    			par(hanging-indent: it.hanging-indent, item.description),
    		)
    	).flatten(),
    )
    

Notes:

  • You can use terms.property in many places instead of it.property.
  • Both solutions still, although artificially, respect the set terms rule. That is, with the exception of tight and spacing.
  • In the first attempt, I suppose the loop isn’t strictly necessary, but I also couldn’t find away to merge it with the rest of the solution.
  • The second solution involving a grid is partly inspired by @PgBiel at the Typst Discord on July 13, 2025. At the moment, I believe this is the best you can do to customise terms. It might as well be comparable to the situation with list and enum items which are bound to become more customisable in the future.
  • I generated both outputs using these settings:
    #set par(
    	justify: true,
    	first-line-indent: 1.2em,
    )
    
    #set terms(
    	separator: [:] + h(.65em),
    	hanging-indent: 6em,
    	indent: 4em,
    )
    

If I missed the mark, please attach screenshots of the desired output.

There is the package tabbyterms – Typst Universe that works well for that.

#import "@preview/tabbyterms:0.1.0" as tabbyterms: terms-table

//#show: tabbyterms.style.default-styles // Un-comment to remove borders

#terms-table(align: right)[
  / Package: PACKAGENAME
  / Technology: Typst
  / Subject: General, Mathematics, Linguistics
  / Category: Layout, Components
]

Highly customizable. As the docs say:

terms-table
Convert a term list element (terms) to a table
Additional arguments are forwarded to the table function

1 Like

Thank you very much for your answers but this is not what I’m looking for. I want a terms list that looks just the same as the standard terms list, but with the term right aligned up to the hanging indent. Crucially, if the term is too long for the indent, it should flow into the paragraph, like the standard terms list.

My code does what I want, except for the extra spacing between the terms. It does not respect the tightness of the terms list and I don’t know how to do that.

The yellow is the spacing that is added by the pad(left)

I think you can add the following first in your context block to adjust the spacing:

set block(spacing: par.leading) if terms.tight

Each pad is a block in this case. Switching between using par.leading and par.spacing between the parts is more or less exactly what the tight toggle does.

Thanks, that solves my problem. The place with spacing underneath still remains an ugly hack, but I guess nobody is going to look under the hood.

Perhaps you would consider renaming your question to something more in line with what you were looking for? The accepted answer is not about “styling the terms right align in a terms list”.

I don’t know, I didn’t write this title in the first place. The original title of this topic was changed underneath me, so whoever did that can do it again, I guess?

For what it’s worth, this is the full working code if you want the terms in a term list right aligned.

#let rightalignedterms(body) = {
    show terms.item: it => context{
      set block(spacing: par.leading) if terms.tight
      let label = strong[#it.term#terms.separator]
      let width = measure(label).width.to-absolute()
      let indent = terms.hanging-indent.to-absolute()
      let lspace = 0pt
      let rspace = 0pt
      if width < indent {
        lspace = indent - width
      } else if width > indent {
        rspace = width - indent
      }
      pad(left: terms.indent, { 
        place(float: false, [#h(lspace)#label])
        pad(left: indent, [#h(rspace)#it.description])
      })
    }
  body
}

The effect will only show itself if terms.hanging-indent is larger than the majority of the terms in your list.

When we write it like this:

/ t1: description 1
/ t2: description 2

Then t1 and t2 specifically are the terms, where I hope you agree? I didn’t read the question correctly on the first try either but I think with context that the title is correct, it’s about the terms specifically being right aligned.

The picture in a previous bdr post provided the necessary context for me, for one, for example it provides an interpretation what right aligning terms means and how to handle too-long terms. (We could squint and see that we have one “column” of terms, right-aligned, standing against one “column” of descriptions, left-aligned.)

1 Like