How to display footnotes at the end of a list rather than the page?

Hi all, new Typst user here, and getting on well so far. I have come across a situation where I have a list of bullet points, one of which needs footnote. I’d like that footnote to appear at the end of the list, rather than the bottom of the page, but cannot figure out how to do that. Been playing with blocks, but that hasn’t fixed the issue, and has created others instead. Is this actually possible?

James

1 Like

flaribbit once posted a solution on GitHub:


#let notes = state("notes", ())
#let content-box(body) = block(
  stroke: 1pt,
  width: 10cm,
  inset: 8pt,
  {
    body
    parbreak()
    context for (i, e) in notes.get().enumerate() {
      [#(i + 1) #e]
      parbreak()
    }
    notes.update(())
  },
)
#let content-box-note(text) = {
  notes.update(s => {
    s.push(text)
    s
  })
  context notes.get().len()
}

#content-box[
  #lorem(20)
  #content-box-note[hello world]
  #lorem(20)
]

#content-box[
  #lorem(20)
  #content-box-note[hello world]
  #lorem(20)
  #content-box-note[hello world]
]

Is this a thing? I’ve never heard of such footnotes. Which is also probably why Typst doesn’t support that, so you will have to rely on some custom function like the one @Y.D.X provided.

Thanks all. The reasoning is that the list is in a section, there a new heading soon after. If the footnote goes to the bottom on the page, rather than just under the list, then it just looks a bit odd as the new section splits it away from its location. I’ll get round it by just manually adding my own superscripts.

1 Like

I actually had to do a similar thing recently because page footnotes are not allowed, so I just added a line of text with a superscript under a table in a figure.

Also, if you found a solution post, be sure to mark it as such.

This seems to be somewhat common for tables, e.g. Wikipedia (list of Austrian governments). The footnotes follow the table and are not put at the bottom of the article.

btw @James_Hughes is there something specific preventing you from using the solution that @Y.D.X showed? If so, maybe we can refine it if we know what you need.

In the end I used a simple superscript inline and manually added a line of text under the section (which was a table IIRC). Y.D.X’s solution was a little overkill for my single occurence needs.

1 Like

Have extended this to match the format and behavior of regular footnotes more closely, and called it “local footnotes”:

GitHub Gist

Code (possibly outdated, compared to Gist)
#let _local-footnotes-state = state("local-footnotes", (
  numbering-format: "a",
  notes: (),
))
/// (internal) unique ID per group of local footnotes
#let _local-footnotes-counter = counter("local-footnotes")

/// Changes the setup for local footnotes
#let local-footnotes-setup(numbering-format: "a") = {
  context {
    if _local-footnotes-state.get().notes.len() > 0 {
      panic("cannot change local-footnotes-setup when there are not yet shown footnotes")
    }
  }

  _local-footnotes-state.update(s => {
    s.numbering-format = numbering-format
    s
  })
}

/// (internal) Creates labels for linking from ref to note and vice versa
#let _local-footnotes-labels(footnotes-group-id, index) = {
  (
    ref: label("_local-footnotes-ref-" + str(footnotes-group-id) + "-" + str(index)),
    note: label("_local-footnotes-note-" + str(footnotes-group-id) + "-" + str(index)),
  )
}
#let _local-footnotes-format(notes) = {
  set text(size: 0.9em)
  parbreak()
  // Indent notes, similar to what `footnote` does
  pad(left: 1em, notes)
  parbreak()
  v(1em)
}

/// Shows the local footnotes which have been added so far, and resets the list of footnotes.
/// The `format` parameter can be used to overwrite the default formatting of the footnotes.
#let local-footnotes-show(format: _local-footnotes-format) = context {
  let local-footnotes-state = _local-footnotes-state.get()
  let footnotes = local-footnotes-state.notes
  if footnotes.len() == 0 {
    panic("no footnotes to show")
  }
  local-footnotes-state.notes = ()
  _local-footnotes-state.update(local-footnotes-state)
  _local-footnotes-counter.step()
  let footnotes-group-id = _local-footnotes-counter.get().at(0)
  let numbering-format = local-footnotes-state.numbering-format

  format({
    let footnotes-entries = ()

    for (i, note) in footnotes.enumerate() {
      let number = i + 1
      let formatted-number = numbering(numbering-format, number)
      let labels = _local-footnotes-labels(footnotes-group-id, number)

      // First add the footnote number
      footnotes-entries.push([
        // Set link color to regular text color, but only for this link, not for links in note text
        #show link: set text(fill: text.fill)
        #link(labels.ref, [#super(formatted-number)])#labels.note
      ])
      // Then add the corresponding footnote text
      footnotes-entries.push(align(left, note))
    }

    grid(columns: (auto, 1fr), column-gutter: 0.2em, row-gutter: 0.7em, ..footnotes-entries)
  })
}

/// Creates a local footnote with the given text.
/// Can provide more than one note, to separate the footnote references with a comma.
#let local-footnote(note, ..additional-notes) = {
  // Similar to regular `footnote` support placing a label on a local footnote
  // and then referring to that again at a later point
  let is-label = type(note) == label

  if not is-label {
    _local-footnotes-state.update(s => {
      s.notes.push(note)
      s
    })
  }

  context {
    let local-footnotes-state = if is-label {
      // TODO: This implementation might be rather error-prone because it also 'works'
      // for labels not attached to local footnotes (?)
      _local-footnotes-state.at(note)
    } else { _local-footnotes-state.get() }
    let number = local-footnotes-state.notes.len()
    if is-label {
      // Apparently at the position of the label the state has not been updated yet (?),
      // therefore increase the number
      // TODO: This is rather brittle, it only works because the referenced note itself, which
      //   is not accessible here due to the state not having been updated, is not necessary
      //   when referencing an existing footnote
      number += 1
    }
    let formatted-number = numbering(local-footnotes-state.numbering-format, number)
    let footnotes-group-id = _local-footnotes-counter.get().at(0)
    let labels = _local-footnotes-labels(footnotes-group-id, number)

    let color = text.fill
    show link: set text(fill: color)
    // Only add label if this is not itself a label referring to an existing footnote
    let ref-label = if not is-label { labels.ref }
    // Use `weak: true` to collapse space, similar to built-in `footnote`
    [#h(0.1em, weak: true)#link(labels.note, super(formatted-number))#ref-label]
  }

  for note in additional-notes.pos() {
    super(",")
    local-footnote(note)
  }
}

It basically tries to recreate / mimic built-in footnotes, without actually using (or being able to use) any of the built-in footnote functionality.
Seems to work ok, but maybe there are better ways to implement this (see also the TODOs in the code).

Here is an example:

#local-footnotes-setup(numbering-format: "1)")
#figure(caption: [Table with footnotes], {
  table(
    columns: 2,
    table.header[*Number*][*Word*],
  
    [1], [one #local-footnote[the number 1]<fn:number-one>],
    [2], [two #local-footnote[the number 2]],
    [1], [one #local-footnote(<fn:number-one>)],
  )

  align(left, line())

  local-footnotes-show()
})

Rendered:

1 Like