Overriding template parameters; missing social convention or Typst design flaw?

TL;DR: in my experience it is difficult or impossible to “tweak” Typst templates to change some layout policies without copying and modifying its source code. Is it just that we are collectively bad at writing templates, or a fundamental design flaw or missing feature in the Typst language?

Context

I’ve been using Typst for various documents for a few months now (free-form documents, talk slides, more structured document looking like math research papers). My experience is that “packages” work as I expect (I import them, use them, I can find documentation online etc.) but that “templates” (packages that define the layout of the document, corresponding to LaTeX document classes) never work in the way I would expect, which comes from my experience with LaTeX templates.

In LaTeX-land the usual experience is to use a document class, and then tweak it (in the preamble of my document) to get the desired result. Maybe I want to have larger margins, change the styling of sections (but not subsections), whatever. It is very easy to do. In Typst, my experience is that this is often unpleasant, sometimes impossible, and I always end up copying the template in my local repository and modifying its implementation.

This observation is coherent with the fact that there are tons of templates online: people don’t seem to reuse each other templates, they copy-paste, modify, and then releasr their own.

Concrete example

I wanted to write a math document so I decided to start from an existing math template. I randomly picked the unequivocal-ams template, which is maintained by the Typst authors (so: presumably it embodies best practices in writing templates), and described as

An AMS-style paper template to publish at conferences and journals for mathematicians […] It also is a nice starting point for a classy tech report or thesis.

I import the package and notice that level-1 headings are in smallcaps, which may be what the AMS does but I don’t like it. Here is a simple repro case:

#import "@preview/unequivocal-ams:0.1.2": ams-article, theorem, proof
#show: ams-article.with(
  title: [A simple math document],
  paper-size: "a4",
)

= My first section

Some content.

How can I, from the document, tweak the package to change the level-1 headings to not be smallcaps anymore? This should be easy with #set or #show rules, right? (Or maybe an option of the ams-article.with function?)

Wrong! Or at least I don’t know how to do this. The problem is that unequivocal-ams has no parameter to toggle this, and defines all its heading style choices in a single show heading: it => { ... } block (see the current source code). The only way I found to override this show is to repeat show heading: it => { ... } myself, but this removes all the styling choices for all headings, not just the styling of level-1 heading. If the package defined this heading-style function as an exported variable, I could call it again from my own document and also do some overrides. But it does not.

The only solution I found is to re-implement all heading styles in my document, by copying the template definition and then simplifying it away.

The question

How can we make it easy to use a template and let users tweak aspects of it? I currently see the difficulty of doing this as a major flaw of Typst compared to LaTeX.

  1. Maybe I’m just unlucky in my package choices or missing something obvious, and there is no problem?
  2. Is this just a question of social convention, there is a way to write template packages in easy-to-override style (which one? are there best-practices described somewhere?) and we should ask people to follow it? For example, maybe all style choices should be controlled by mutable variables that users can change, or pass as package config parameters? Or maybe all layout functions in the template should be named explicitly, to allow a user-defined callback to use a form of “inheritance” by calling them?
  3. Or is this a fundamental missing piece in Typst the language? Do we need a first-class construct for tweakability, similar to the existing research on Aspect-Oriented Programming? Emacs has a defcustom mechanism for variables and defadvice for functions (and generous use of dynamic binding) to support similar forms of configurability.
8 Likes

Hi @gasche, welcome to the Typst forum!

I guess Typst suffers from both missing social conventions and design flaws (there’s always room for improvement, and Typst is very young). But in my experience it’s better than LaTeX for customization, except for those cases where the LaTeX class/package author offers an option to do exactly what you want, and indeed many LaTeX packages have a gazillion of options, so using a new non-trivial LaTeX package feels like a project of its own. I think Typst is more empowering as it’s much easier to adapt things yourself, and you generally don’t need to read a 50 pages manual to get started with a new package. Of course this is a bit subjective. But let me address some of your points one by one:

A reasonable assumption :slight_smile: but this code is two years old which is like half the age of the universe in Typst time. The Typst authors would probably do some things differently if they rewrote the template today.

I think the easiest would be

#show smallcaps: it => it.body

This replace every smallcaps function call with its argument, effectively removing the call.

Or better, to avoid affecting other pieces in smallcaps:

#show heading.where(level: 1): it => {
  show smallcaps: it => it.body
  it
}

(this one must come after the #show: ams-article call)

You can do it for only level-1 headings by using show heading.where(level: 1): it => { ... } instead.

I think it’s better to avoid offering a gazillion of options as done in the LaTeX world when there’s a reasonably straightforward way to accomplish something with the standard Typst commands. This way there’s less duplication of ways to do things and as you learn more standard Typst you can use your knowledge with all packages instead of having to learn a new mammoth API for every package. But yeah sometimes it’s not so straightforward…

That’s a good point: template packages are more useful when they are written in a modular way and export the different pieces (on top of the main template) so you can easily use some bits directly. It’s not that common currently in the ecosystem unfortunately.

There’s indeed an important piece currently missing: the possibility to define custom types and have set rules work with them. The Typst authors have said that this is planned (I think), and it will probably have a huge impact on how packages/templates can be configured, making things much easier.

In my experience tweaks are not so easy in LaTeX… Changing margins was a nightmare before a good soul made the “geometry” package, and I don’t think that plays well with all classes, e.g. with those in KOMA-Script. In general the only way to get something done is to find an online post from someone who had the same problem and copy-paste the abstruse solution that I could never have found myself :sweat_smile:

To take the present example: how would you do it in LaTeX (disable the smallcaps of section titles for the amsart class)? Searching for solutions online I found this on tex.stackexchange which is I think more complicated that in Typst?

5 Likes

Hi as well @gasche

Just want to chime in on this conversation.

Isn’t that a bit too much of an expectation? I understand your side, as I’ve also had LaTeX experience before using Typst and I might have had the same thoughts when first using Typst (I first disregarded Typst as a contender, as I was enjoying LaTeX a lot more → but this has changed). Typst still has no major version number, so it’s very much an in development product/software and could therefore profit from the users’ opinions.

What I enjoy about Typst is its ease of use, once you understand the syntax. When a template bothers me, I can simply copy-paste it into my own project and adjust the desired components.

And as I always like to say: if it bothers you too much, adapt the template to a new feature-rich template or pull-request your desired features to the respective template-repository.

On the contrary, I think we should talk about this early:

  1. Maybe we are collectively missing things that are possible today. @sijo had nice recommendations for how to perform the particular bit of tweaking I was after. Some of it is natural in retrospect, but is not the sort of things that beginning-to-intermediate Typst users may think of. It may be that we just need to write a “how to override stuff” section in the documentation to explain a bag of tricks – and talking about this now might help get this to happen. The documentation problem is that authoring templates and tweaking templates are similar skillsets, but many people don’t need to be come expert at authoring templates yet need enough knowledge to tweak templates.

  2. If there is a missing convention for template authors, style guidelines to improve after-the-fact tweakability, then the Typst community should figure these recommendations/guidelines now and try to improve the packaging ecosystem.

    Personally I view the “just copy the code and hack it around” approach as a clumsy workaround that delays collective progress on this front. For example, what do you do when the template file evolves to a newer version, you want some of its nice stuff, you get to manually merge your own changes into the new version each time? Sounds like a world of pain when used at scale.

  3. If there are language-level features to consider adding or improving to make overriding/tweakability easier, then the earlier we talk about it the better, when it is “still in development phase”, so that these additions may be considered.

4 Likes

I agree with you, that some kind of templating resource and style guidelines should be created.

The problem with the current eco system is that each Typst update breaks templates and packages to some degree. It is a necessary step, as the developers figure out what exactly they want from their project. The “just copy the code and hack it around” is just the current state of the templating feature set and clumsiness is of human nature, as long a goal can be reached ;)

The community shouldn’t figure this out by themselves, but include the devs into this to find some kind of compromise! An approach I imagine could be useful would be to create a documentation pull request, which introduces some elements of your mentioned wishes (typst/docs at main · typst/typst · GitHub → for a starter :) ).

Also, for me @sijo’s approach is the hacky approach and is unfortunately the current state of how tweaking elements is done.

@laurmaedje Sorry to include you into this, but I think it’s an useful discussion. What would your opinion be on this subject?


An idea I have is some kind of weak definition of a set or show rule (similar to __weak functions in C), which could allow overwriting/replacing template definitions. Depending on the usage, this could also turn into a Frankenstein monster. The “weak” rule would simply revert back to the Typst default, when the rule is overridden.

// in the template file
#let template-xyz(...) = {
...
  weak show heading.where(level: 1): it => {
    emph(it)
  }
...
}

// in the main doc
#override show heading.where(level: 1): it => {
  strong(it)
}
// or simply
#show heading.where(level: 1): it => {
  strong(it)
}

This solution might introduce some problems, which I am currently not aware of.

Another approach would be to strictly define the parameter list. As an example:

#let template-xyz(
  title: none,
  subtitle: none,
  date: none,
  authors: (none,),
  institution: none,
  options: (
    smallcap-h1: true,
    figure-align: center,
    ...
  )
)

This would streamline the parameter list by limiting it to specific amount of “root”-level parameters and allow for template-specific options.

2 Likes

I’m too busy with other stuff right now to leave an extended response, but in short: Yes. There are both missing conventions and design flaws and our official templates are also not particularly good in that regard.

I think custom types (aka user-defined elements) are crucial to improve the situation. With them, the system becomes much more composable, as they let template authors give semantic meaning to parts of the template and let users select them. We’ll still need social conventions, but without them it’s a bit of an uphill battle I think.

@Mc-Zen has a lot of thoughts on this matter. Perhaps he wants to chime in.

8 Likes

I agree that there is a gap in Typst regarding customizability. I wouldn’t call it a design flaw as that would indicate that the design prevents us from filling the gap, but both design and implementation work is yet to be done to fix the situation.

I’ll link a few resources regarding related topics:

I personally think that custom types will change the answers to the “how to configure packages and templates” question quite drastically, so I’m holding back on trying to find answers right now. It’s a bit contradictory, but I also agree with you that getting some experience with how custom types may behave would help getting them right, so Elembic is pretty valuable in this regard and maybe I should play with it (but I’m also a bit skeptical that using Elembic in a public “production” template is a good idea).

You are asking the right questions and we should definitely think about them before it’s too late; both wrt. custom types and with what problems custom types won’t solve on their own.

4 Likes

I don’t think it’s that hacky… unless maybe only declarative solutions are considered clean and imperative configuration is seen as a kind of hack. I hope you’ll forgive me for going a bit on a tangent here but I think it’s an interesting dichotomy:

Typst has both declarative rules (set xxx and show xxx: set ...) rules and imperative rules (show xxx: it => ...) which is great: declarative code is generally more readable, easier to analyze and optimize, it composes better, etc. so people should use show-set rules as much as possible. But they are limited to the control knobs that have been explicitly designed in the language… For anything else you need the power of imperative rules that give you a whole programming language to replace an element with anything you want.

The unequivocal-ams template uses an imperative rule to style the headers:

show heading: it => {
  // Create the heading numbering.
  let number = if it.numbering != none {
    counter(heading).display(it.numbering)
    h(7pt, weak: true)
  }

  // Level 1 headings are centered and smallcaps.
  // The other ones are run-in.
  set text(size: normal-size, weight: 400)
  set par(first-line-indent: 0em)
  if it.level == 1 {
    set align(center)
    set text(size: normal-size)
    smallcaps[
      #v(15pt, weak: true)
      #number
      #it.body
      #v(normal-size, weak: true)
    ]
    counter(figure.where(kind: "theorem")).update(0)
  } else {
    v(11pt, weak: true)
    number
    let styled = if it.level == 2 { strong } else { emph }
    styled(it.body + [. ])
    h(7pt, weak: true)
  }
}

Among many things this wraps every level 1 heading in a smallcaps element. You need an imperative rule for that, and it seems natural that an imperative rule is required to undo just this part of the code. I find it rather elegant that Typst lets you do it with a simple show smallcaps: it => it.body which basically reads as “remove the smallcaps wrapper”.

Maybe a future version will let us wrap/unwrap elements declaratively… And when Typst gets custom types, third-party packages/templates will be able to extend the range of things we can configure/override with set and show xxx: set .... But for users, declarative rules will still be limited to predefined settings so imperative rules will always have a place, not as hacks but as legitimate solutions.

This sounds similar to the idea of revokable show rules. If that gets implemented it would allow for a nice declarative solution, but again only if the template author thinks of making the smallcaps wrapper “configurable” by isolating it in its own show rule and labelling or exporting the rule.

(I think your idea with weak rules is bit different as it would not require labelling/exporting the rule, but then it would not be flexible enough: you might have 4 weak rules applied on an element and want to override only one of them…)

I’m not sure having sub-settings in options makes a big difference. In the end either the option is supported by the template or it is not (or has a different syntax), and you need to adapt your code when you switch templates.

But yeah having more template options is another way to offer declarative settings, and it’s great for things that are not easy to configure/override otherwise. Maybe it would make sense to have smallcap-h1 in there. But this can’t possibly be a general solution… Just looking at the heading show rule above, to cover all uses cases you’d need a gazillion of options:

  • center-h1
  • number-hspace
  • v-above-h1
  • v-below-h1
  • v-above
  • v-below
  • style-h2
  • reset-theorem-h1

Basically every line of imperative code would require a setting, which means a huge API to learn, a huge manual to write, and a more complex code base since these settings must be declared in code and every line replaced with if setting-xxx { ... } else { ... }.

So I think it shouldn’t be the goal to make everything configurable/overridable declaratively. Instead we should aim to

  1. make as many things as possible easy to override with standard Typst code (either declarative or imperative), and
  2. offer settings for things that are difficult to override with standard code.

A concrete example

How can I disable the centering of level 1 headings with unequivocal-ams? Should the template offer a center-h1 setting?

It sounds like something trivial that doesn’t need an explicit setting because a simple show-set rule will do, but unfortunately it’s not that simple (as far as I can see).

You can “override” the set align(center) with something like show heading.where(level: 1): block to make the align ineffective, but that will also affect the spacing (and probably other things) so it’s not a clean solution.

It touches on the main issue I encounter when trying to compose show rules: composability breaks down when a rule for element of type X doesn’t return an element of type X (see previous discussion here). Here the heading show rule returns

smallcaps[
	#v(15pt, weak: true)
	#number
	#it.body
	#v(normal-size, weak: true)
]

No heading is returned. By chance the returned content is an element (smallcaps) that I can select on, so I can do

#show heading.where(level: 1): it => {
  show smallcaps: set align(left)
  it
}

but that’s definitely a hack…

Ideally you’d like the show rule to return a heading with the smallcaps as body, but that introduces all kinds of issues with numbering and styling and recursion.

Maybe the solution will be user types: if the show rule returned an ams-heading I could simply do show ams-heading: set align(left).

It might even fix composability if ams-heading could be made a child type of heading or something like that, so that a show rule on heading placed before show: ams-article would still be applied even after its transformation into an ams-heading?

3 Likes

My apologize, I didn’t intend to shoot at you. I think it’s more that this is just how currently it is done. And no worries, I also have to learn some lessons :sweat_smile:

That is actually more on what I thought about → I think it would be interesting when combined with custom types. Let the template define its rules, which then can be “revoked” by the user, if they don’t like specific implementation. I’d imagine some kind of “default”-getter would make more sense → Restyle a component by first grabbing the default of it and create your own rule.

For me it helps a little by grouping things together → When using it I know that the dictionary option contains…well…options for me to configure!

Of course, this just an idea and I totally understand the disadvantages it produces (the bloat)

Those are really good points! I think this would make more sense, especially with the ams-heading example. Maybe when the template is imported through #import, it declares the various components (ams-heading for example), which can be configured via show and set. Once the template show rule

#show: template

is called, the components should be applied to the document. Now it would act as a normal Typst document → adjusting a component mid document would just change the following content, not all of it.

Now how this could be implemented is a big mystery to me…


It would be interesting to have the templates used as an everything show rule:

#show: <template-name>

though it might not allow for multiple templates to be used in a doc. Maybe more in a “remap” way. The template has some kind of map function, which is automatically called and maps for example ams-heading to heading.

#show: charged-ieee

content here

#show: normal-ieee // maybe only apply on the following pages?

blabla

Hi everyone (thanks for pinging me @laurmaedje, I was too busy to make a post in the last two weeks)!

I recently put a lot of thought into template design and think that the issue is three-fold:

a) How can the user of a template tweak styles that are set or shown by the template?
b) How can the user manipulate the content (like a title page) that the template produces?
c) How can the user of a template define things like authors, title, abstract might be used in more than one place of a document?

For now I want to address the first two parts.

1. How templates apply styles

In my opinion, one of the biggest issues is that templates do not separate styles from content.

Let us start by thinking about the default style. Every type (like heading, figure, etc.) defines a default show rule that determines how it looks. The way the initial style is set up, it is super easy to customize everything - well with two exceptions, one of which is heading which out-of-the-box comes with a bold and slightly bigger font which is not entirely trivial to undo.

But the main thing here is that the document starts with this style before any content is produced which allows us to change everything like the page size upfront.

I believe that packages need the same approach. First, set up some nice default styles (purely through set and show rules) and then immediately give it up to the user to tweak and change these styles, e.g.

// -----  template.typ
#let template(body) = {
  set page(margin: 2em)
  set text(11pt)
  body
}
// -----  main.typ
#show: template

Currently that is not what all templates I know of do. They set up styling and also produce content right away, like this:

// -----  template.typ
#let template(body, title: []) = {
  set page(margin: 2em)
  set text(11pt, font: "DejaVu Sans")
  // problem:
  align(center, text(weight: "bold", title))
  body
}

// -----  main.typ
#set page(margin: 1em) // is overwritten :(
#show: template.with(title: [Typst])
#set page(margin: 1em) // works only from second page on :(

The style of the content that this template above produces cannot be freely styled. In particular, we cannot change the page margins on the first page, nor can we change the font of the title (as demonstrated in the code above)!

It becomes evident that we need a cut!

The usage of a template would need to be structured like the following:

  1. The code
    #import "template.typ"
    #show: template.style
    
    should only change the style defaults and not produce any content. It’s like we start with an empty document after that.
  2. Now is the place to intervene and tweak the style that the template has set up − just as we do with the “default style” (note that for some things we need revoke rules as described by @sijo above).
    #set page(margin: 1em) // now we are safe. 
    
  3. Finally we can give the template the opportunity to produce content!
    #show: template.content // (not the best name yet)
    

Yes, this does make applying template a two-step procedure but I think it is the only way to get out of this awkward situation. Otherwise, things like the title page generated by a template will never be nicely re-stylable.

There is actually another way but it is flawed

Consider the example of a generated title page and imagine user-defined types are already available. A template designer could define a type

#type titlepage(
  field: title,
  ...
  show: it => ..
)

(very simplified, titlepage may be complex) and would use it in the template

#let template(body, title: []) = {
  set font(11pt)
  show ...
  titlepage(title)

  body

  // more content that the template injects here
}

Now the font size of the content produced by the template becomes accessible!

#show titlepage: set text(9pt)
#show: template
#set text(9pt)

Unfortunately, you need to use set text(9pt) twice which I find uncompelling, to say the least. Also, the end-user needs to know a lot about the template for more involved (and realistic) scenarios.

2. How templates generate content

The second problem from the list above is that templates generate complex content that cannot be properly customized, just like the title of the ‘standard’ template:

  align(center)[
    #block(text(weight: 700, 1.75em, title))
  ]

No way to customize that one properly.

Instead, templates should define different types for, e.g., the title.

#type title {
  field body
  show it => block(it.body)
}

and in the styling part of the template (see above) write default show rules for it. In this case:

// in template.typ
#let styles(body) = {
  ..
  show title: set align(center)
  show title: set text(weight: 700, 1.75em)
  body
}

The user can simply and selectively override these rules. Even more power is granted by writing full show rules, e.g.,

// in main.typ
#show template.title: it = circle(it.body) 

In particular, we need types to achieve this because they give us fine-grained and selective control over the styling.

15 Likes

Thanks everyone for the excellent comments. I will need to read about the works-in-progress on user types and revoke rules to go more into the details.

@sijo I was initially convinced by your #show rules to disable smallcaps, in particular

#show heading.where(level: 1): it => {
  show smallcaps: it => it.body
  it
}

but in fact I now believe that they are subtly broken: if the user wanted to put smallcaps words in their section title, it would remove it as well. (This is not a purely theoretical concern, I routinely use smallcap names in my documents.)

1 Like

That makes a lot of sense. Though I’m afraid many cases could not be expressed with this nice separation. For example how would it work for more complex layouts? Consider the title of the diatypst template (full code here):

if (type(authors) != array) {
  authors = (authors,)
}
set page(footer: none, header: none, margin: 0cm)
block(
  inset: (x:0.5*space, y:1em),
  fill: title-color,
  width: 100%,
  height: 60%,
  align(bottom)[#text(2.0em, weight: "bold", fill: white, title)]
)
block(
  height: 30%,
  width: 100%,
  inset: (x:0.5*space,top:0cm, bottom: 1em),
  if subtitle != none {[
    #text(1.4em, fill: title-color, weight: "bold", subtitle)
  ]} +
  if subtitle != none and date != none { text(1.4em)[ \ ] } +
  if date != none {text(1.1em, date)} +
  align(left+bottom, authors.join(", ", last: " and "))
)

I guess the template should define a user type for title, subtitle, authors, date. But also a different user type for each block? That’s a bit weird and a lot of boilerplate. And that’s still a fairly simple example. Templates can use nested grids, styles that depend on the value of it, etc.

So I think the language also needs a robust story for inspecting and rebuilding elements with show-it rules.

This will replace a title element with a simple circle, so all the styles (text color, etc.) that the template specifies for title will fail to be applied…

I guess what you want is more like

#show template.title: it => template.title(circle(it.body))

but then you actually need something more complicated to avoid recursion.

Maybe this points to another thing that could be improved:

  • Make it easy to write non-recursive show rules
2 Likes

Damn right, and I see no easy fix (and I think the LaTeX solution mentioned above has the same problem!). Again this would be fixed with custom types: if the template emitted a custom ams-heading element, we could make a show rule on that to remove just the top-level smallcaps.

Maybe that’s another piece currently missing:

  • Give an easy way to say “apply this show rule only to the topmost-level thing matched by the selector”.

That’s the point I made with the user-defined types for such things. There is a trade-off: if you want to customize everything to the smallest detail of course you will need to read the documentation of the template. There will never be a way around that. But if you want that level of control then you should maybe write your own template. But the point of this discussion is not how to make literally everything easily configurable, that does not make sense. But easy things should be easy, which is the point that my suggestions intend to point at.

I don’t think you need to write a different type for each block. But maybe for title, author, subtitle. Experience will tell.

This will replace a title element with a simple circle, so all the styles (text color, etc.) that the template specifies for title will fail to be applied…

You’re right, I made this example out of my head and didn’t pay enough attention. I wanted to make clear that you can entirely restructure the visualization of a type by show .. : it => .. rules (and accessing the different parts of the type), so actually my example does what I desired but it’s a bad showcase. In this case you will want to write show template.title: circle, however. This is the best version and naturally avoids recursion and all the other issues.

There might be lesson to be taken from HTML/CSS. A crude way to explain the Typst styling model is that the document produces a document tree that can be compared to an HTML document, and then the set/show directives “style” it be reprocessing the tree, and can be compared to CSS sheets (but produce new content instead of just styling the content). Some remarks that pop to mind:

  • CSS has a much richer selector language, and some of it could help. @sijo remarked above that a “first child” selector would help with the smallcaps example. (It’s also a part of Typst that may be extensible without too much of a performance or complexity-budget impact.)

  • Adding user-defined types into the mix would correspond in HTML/CSS land to letting users define their own HTML elements/tags. This is not done in HTML, but it was the common practice in the old days of XML/XSLT.

    • Note that “custom tags” in HTML/XML do not need to be declared before they are used, which makes them more lightweight to use. I have doubts that a feature that requires type declarations before usage (nominal rather than structural type) is going to get as much traction from template authors, without strong social conventions to enforce it.
  • HTML actually does not rely on custom elements/tags, but on class and id. id is intended to uniquely denote a single element, so they correspond directly to Typst references. class lets you annotate several elements with a common intent that is used for styling. This is similar to using a common custom types for those elements, but a single element can belong to several classes. I don’t know if that form of customization would be useful for Typst documents.

3 Likes

Sometimes you want to put something “around” the show target, and this version is perfect. But sometimes you want to put something “inside”. For example if you want to prefix the title with "Title: ", you want this to be part of the title element on the page so that it gets the same size, color, etc. and then I think you need my version with the recursion issue…

Typst templates are fairly small (LOC) therefore I would suggest that instead of adjusting a template by putting a layer of customisation on top of the template:

template → template overrides → document

I would suggest to copy (fork) a template and adjust it directly:

modified template → document

You get all the benefits of modern version control: you can have your own version that you can share and reuse, you can have adjustments on a separate branch and pull any upstream fixes on your paste. People that are not skilled with version control can just copy the template files and adjust them locally.

That’s a good alternative of last resort, and a bit more approachable than doing the same with LaTeX classes because a Typst template’s code is more readable (imo). Still, I feel that OP is right that 1) it’s worse user experience than being able to configure a template and 2) having to do it is an indicator that Typst is not capable enough.

Also, note that maintaining a fork is more complex than using configuration knobs. If the configuration changes, you’ll (hopefully) get migration instructions in a changelog. If you have a fork, you’ll have to deal with merge conflicts. (Both of course assuming that your use case makes updating the template desirable.)

4 Likes

There are many obvious downsides to the approach that you are suggesting, which is what many people do today in Typst-land in practice – whenever I broach this topic to a fellow Typst user, they admit that they’ve been doing a lot of this. For example:

  1. This makes it harder for people to share their improved templates as reusable blocks. Instead of one upstream template that grows with tweakable options, you have many local improved copies, which are difficult or impossible to merge back into a coherent document. Some of this is inevitable anyway, but favoring source-level modification over after-the-fact parametrization makes things way worse. Less collective progress in the template ecosystem.

  2. This forces people to be comfortable with authoring and maintaining templates, when what they want is (mostly) to customize/parametrize them. I think that ecosystems work well when “using” libraries is notably easier than authoring them. (Think C++ templates: requires expert-knowledge to write well, but only intermediate-level knowledge to use.)

  3. Updating modified-templates to new upstream versions will be a chore, and people will not do it in practice. We’ve seen that with the PHP ecosystem when the default way to reuse libraries was to vendor them into your project. People would never update their vendored copies. Typst documents will be the same – no one is paid to cleanup Typst documents to reduce tech debt, so the worse practices of least effort will win.

  4. A notable share of scientific editing works with LaTeX, and expects to receive many independent articles written against the same TeX document class, and merge them into a single document that can be compiled to produce coherent proceedings (with globally-chosen page numbers, a table of contents, etc). This workflow is incompatible with a situation where each author has a different version of the basic style template. Favoring this invasive-modification style will make it harder for (some) professional LaTeX users to adopt Typst.

5 Likes