I noticed that subscripts and superscripts are not placed correctly when the size of a delimiter is changed (made taller/bigger for example). For example if you manually adjust the height of ) or ] the subscript is no longer vertically aligned correctly. I wrote a short code to fix this issue. Hopefully this can be handled automatically by Typst in future versions.
I cannot upload the file so I am pasting the content below.
// math-fences.typ
//
// Delimiters whose height you choose, with subscripts and superscripts placed
// against the closing glyph instead of dangling below a tall pair of bars.
//
//
// Why this exists
//
// A script in Typst binds to the atom before it, and the built-in `norm`, `abs`
// and auto-scaled `( )` grow to fit whatever they wrap. Together those two facts
// mean `norm(sum_(i,j) a_(i j))_(S_1)` puts the subscript at the bottom edge of
// full-height bars, well below the baseline. Shrinking the bars with
// `lr(..., size: ...)` does not fix it, because the script still attaches to the
// whole group.
//
// The delimiters here tag themselves with `metadata`. A `math.attach` show rule
// spots the tag and rebuilds the expression, attaching the script to the closing
// glyph at a height you control. You keep ordinary `_` and `^` syntax.
//
//
// Usage
//
// #import "math-fences.typ": fences, norm, absv, parn, brak, brac
// #show: fences // installs the show rule
//
// $ norm(sum_(i,j) a_(i j) e_i e_j^T)_(S_1) $
// $ norm(x, h: #1.5em)_(S_1)^* $
// $ parn(sum_j sigma_j (A)^p)^(1/p) $
//
// If a template already applies the rule (see `show math.attach:` below), you do
// not need `#show: fences` as well.
//
//
// The h argument
//
// A length gives a fixed height, so every delimiter you pass the same value
// comes out the same size. A ratio scales each delimiter to its own content.
// Ratios below 100% are the point: the delimiter has to be shorter than what it
// wraps for a subscript to sit near the baseline. At 100% you are back to the
// built-in behaviour.
//
//
// Gotchas
//
// - Called from math mode, value arguments need a #: `h: #1.5em`, not `1.5em`.
// - Importing `norm` shadows the built-in `math.norm` for the whole file.
// - Without the show rule active these still draw bars, but scripts revert to
// the built-in placement.
// - Any glyph with vertical variants in the math font can be stretched, so
// `#let ceil = fenced.with(sym.ceil.l, sym.ceil.r)` works.
#let fence-bars(l, r, body, h: 80%, sub: none, sup: none) = context {
let hh = if type(h) == ratio {
h * measure(math.equation(block: true, body)).height
} else { h }
(
math.stretch(l, size: hh)
+ body
+ math.attach(math.stretch(r, size: hh), br: sub, tr: sup)
)
}
// The bars are drawn here as well as in the show rule, so a fence with no
// script attached still renders something.
#let fenced(l, r, body, h: 80%) = {
(
metadata((fenced: true, l: l, r: r, h: h, body: body))
+ fence-bars(l, r, body, h: h)
)
}
#let norm = fenced.with(sym.bar.v.double, sym.bar.v.double)
#let absv = fenced.with(sym.bar.v, sym.bar.v)
#let parn = fenced.with(sym.paren.l, sym.paren.r)
#let brak = fenced.with(sym.bracket.l, sym.bracket.r)
#let brac = fenced.with(sym.brace.l, sym.brace.r)
// Rebuild a tagged fence with the script on the closing glyph. Note that `x_S`
// populates the `b` field rather than `br`, and `x^2` populates `t` rather than
// `tr`; reading only `br`/`tr` drops the script silently.
#let fence-attach-rule(it) = {
let base = it.base
let first = if base.has("children") { base.children.at(0, default: none) } else { none }
let tagged = (
first != none
and first.func() == metadata
and type(first.value) == dictionary
and first.value.at("fenced", default: false)
)
if not tagged { return it }
let m = first.value
let sub = if it.at("b", default: none) != none { it.b } else { it.at("br", default: none) }
let sup = if it.at("t", default: none) != none { it.t } else { it.at("tr", default: none) }
fence-bars(m.l, m.r, m.body, h: m.h, sub: sub, sup: sup)
}
// Apply with `#show: fences`.
#let fences(body) = {
show math.attach: fence-attach-rule
body
}