How can I use special styling in headings without removing numbering

I am seeking to modify the format of headings to allow mixed styling. I’ll lead with the two main sections of code I’ve written for this purpose.

// set up numbering
#set heading(
  numbering: (..n) => {
    let n = n.pos()
    // if this is a first-level heading, use higher font size
    // and roman numerals
    if n.len() == 1 {
      text(
        size: 10pt,
        weight: "regular",
        numbering("I.", n.at(0)),
      )
    // otherwise, use A/B/C, smaller text size, and italics,
    // omitting parent heading labels
    } else {
      text(
        size: 8pt,
        weight: "regular",
        numbering("A.", n.last()),
      )
    }
  }
)

The code above works as you would expect, until I try to style the rest of it.

// set up styling inside the line
#show heading => it {
  // ISSUE #1 (and the main issue): `it.level` is always 1.
  // I assume this is due in part to function purity?
  // If this were not always 1, I could call `numbering` manually.
  if it.level == 1 {
    text(
      size: 10pt,
      weight: "regular",
      // large capital first letter followed by 8pt smallcaps
      [
        // ISSUE #2: I can't find a way to extract numbering content
       // from `it`, and using `it.body.text` strips the numbering away.
        #upper(it.body.text.first())#text(
          size: 8pt,
          smallcaps(it.body.text.slice(1)),
        )
      ],
    )
  } else {
     // ...
  }
}

I’ve denoted my two issues as comments in the second code block. I only need one of them to be worked around.

To avoid an XY problem, I want to clarify that all I’m really looking for is a convenient way to create self-numbered blocks of text with mixed styles inside of said text, in such a way that headings like these can be styled with no extra thought put into it.

= Introduction
== Sub-Introduction
= Conclusion

Thanks in advance.

You should use a set rule for the numbering style and then individual show rules to modify the font size etc…

#set heading(numbering: "I.A")
#show heading.where(level: 1): it => {
  set text(20pt)
  it
}
#show heading.where(level: 2): it => {
  set text(red)
  it
}

The nested set rules will only affect the scope of the show rule, which is the number and body of the heading. You can also add nested show rules if you want to further modify the heading, e.g. with the smallcaps() function.

The flexibility of set rules and show rules is nicely explained in the styling section of the documentation here. Since your initial approaches were way to complicated, you can probably benefit a lot from reading that section.

1 Like

And you haven’t even seen my real code. :wink:

The solution I needed was mentioned on that page you linked. It’s the counter type.

#show heading.where(level: 1): it => {
  text(
    size: 10pt,
    weight: "regular",
    [
      #counter(heading).display(it.numbering)\
//     ^--------------
      #h(15pt)\
      #upper(it.body.text.first())\
      #text(size: 8pt, it.body.text.slice(1))
    ]
}

I appreciate the help! I’m going to mark my own reply as the solution since it contained what I ended up needing, but feel free to call me out if I’m doing that backwards.
Thanks again.