DOI Typst Package User Manual

MIT License GitHub Issues Last Commit

Last updated: March 2026

1 Introduction

This manual introduces phonokit and its functions. phonokit is a Typst package designed to streamline the creation of phonological structures while maintaining typographical precision. Typst is a programming language for typesetting — there is a great tutorial here and an introductory YouTube series here.

The package provides intuitive functions for IPA transcription, prosodic representations (syllables, moras, feet, prosodic words, metrical grids), sonority profiles, IPA vowel charts and consonant tables, autosegmental phonology, multi-tier representations, feature geometry, SPE feature matrices, Optimality Theory tableaux, Harmonic Grammar, Noisy Harmonic Grammar, Maximum Entropy grammars, Hasse diagrams, numbered linguistic examples, and helper symbols. The main goals of phonokit are to minimize effort and maximize quality.

The GitHub repository for the package can be found at guilhermegarcia/phonokit. Comments, suggestions and bug reports are welcome — please open an issue in the repository.

1.1 Installation

Typst packages are loaded with the #import function at the top of your typ document. Replace X.X.X with the version you wish to import:

Package import (Typst Universe)

#import "@preview/phonokit:X.X.X": *

Alternatively, if you want the most up-to-date version, download or clone the repository and load the package locally:

Local import

#import "phonokit/lib.typ": *

You may need a symlink depending on how you structure your files, since Typst restricts imports to files within the compilation root and its subdirectories.


1.2 Font

All functions in phonokit require the Charis font to work as intended out of the box (SIL International 2025). As of version 0.3.7, the user can set a global font to be used by the package throughout the document:

Setting the font

#import "@preview/phonokit:0.3.7": *
#phonokit-init(font: "New Computer Modern")  // add to the top of your document

2 IPA

Try on Snippyst

IPA transcription is likely the most commonly used feature when typesetting documents in phonology. phonokit accomplishes that with the #ipa() function, which takes a string as input. Crucially, the function uses the familiar tipa input (Rei 1996), with a few exceptions (e.g., secondary stress is represented by a comma ,, not by two double quotes "").

2.1 Transcription

Symbols introduced by two backslashes \\ must not have adjacent characters. For archiphonemes, use \\ followed by a capital letter. Thus, while #ipa("N") maps to the nasal archiphoneme symbol rendered with the package font, #ipa("\\N") maps to the small capital N archiphoneme — see the examples in Figure 1.

Figure 1: IPA transcription.

IPA transcription examples

#ipa("DIs \\s Iz \\s @ \\s sEn.t@ns")
#ipa("N \\N R \\R \\I I Z \\Z")
#ipa("p \\h I k \\* \\s \\t tS \\ae t \\s p \\r l iz")

You can download the sheet as a standalone file here.

2.2 Consonants

Two additional functions allow users to quickly create consonant tables and vowel trapezoids given a string of phonemes. The function #consonants() mirrors the pulmonic consonants table in the IPA chart with some minor changes. For example, affricates are shown when affricates: true, and the argument abbreviate: true shortens labels for both rows and columns. Figure 2 shows the consonant inventory for Italian.

Consonant table (Italian)

#consonants("italian", affricates: true, abbreviate: true)
Figure 2: Italian consonants.

The user can either input a language name (Arabic, English, French, German, Italian, Japanese, Portuguese, Russian, Spanish — all lowercase; also all for all consonants) or a string of consonants to create a custom inventory. Custom consonants follow the same input format as #ipa(). Note that affricates and aspirated consonants require curly braces around them as well as affricates: true and aspirated: true, as shown in the caption of Figure 3. The function also supports flexible sizing with the scale argument.

Custom consonant table

#consonants("ts{ts}psS \\*r g{tS} {k \\h}", affricates: true, aspirated: true)
Figure 3: Custom inventory.

2.3 Vowels

The function #vowels() also accepts either a pre-defined language or a string as input. The argument scale is available here too, so the user can adjust the size of the trapezoid as needed.

Vowel trapezoids

#vowels("english", scale: 0.6)
#vowels("french", scale: 0.6)
Figure 4: English vowel trapezoid
Figure 5: French vowel trapezoid

As of version 0.4.5, the function #vowels() accepts additional optional arguments to include arrows, shifted vowels, and highlights. Figure 6 illustrates how arrows and highlight can be used to display diphthongs in North American English. Arrows can be curved (curved: true) or dashed (arrow-style: "dashed"), and colors can be adjusted with arrow-color and highlight-color.

Figure 6: Diphthongs in North American English, with arrows and highlights.

Vowels with arrows and highlights

#vowels(
   "english",
   arrows: (
     ("a", "U"),
     ("a", "I"),
     ("e", "I"),
     ("O", "I"),
     ("o", "U"),
   ),
   arrow-color: blue.lighten(60%),
   curved: true,
   highlight: ("a", "e", "o", "O"),
   highlight-color: blue.lighten(80%),
)

The argument shift allows you to specify a vowel as well as a shift from its original position, enabling much higher flexibility for illustrating dialectal variation. Shifted vowels can be independently targeted by highlight, and arrows can target shifted vowels using the same shifted values. The color and size of shifted vowels can be adjusted with shift-color and shift-size. Figure 7 demonstrates arrows, shifted vowels, and the option to remove grid lines (rows: 0 and cols: 0).

Figure 7: Flexible arrows and shifted vowels.

Vowels with shift

#vowels(
    "aeiouIU",
    arrows: (
      ("a", ("U", -0.6, -0.3)),
      ("a", ("U", -0.3, -0.7)),
    ),
    arrow-color: blue.lighten(40%),
    arrow-style: "dashed",
    curved: true,
    shift: (
      ("a", 0.6, 0.3),
      ("U", -0.6, -0.3),
      ("U", -0.3, -0.7),
    ),
    shift-size: 1.5em,
    shift-color: blue.darken(20%),
    highlight: ("a", ("U", -0.6, -0.3)),
    highlight-color: blue.lighten(90%),
  )

3 SPE

Try on Snippyst

Rewrite rules can be complex, and phonokit provides two primitive functions for feature matrices that can be combined to form SPE-style rules (Chomsky and Halle 1968).

The first function is #feat-matrix(), which outputs the maximal feature matrix for a given phoneme (with the option for 0 values if all: true). This can be useful in introductory courses introducing the notion of distinctive features. The function is based on the features in Hayes (2009). Figure 8 shows matrices for the phonemes in “patchy”.

Feature matrices

#feat-matrix("p") #feat-matrix("\\ae") #feat-matrix("\\t tS") #feat-matrix("i")
Figure 8: Feature matrices for the phonemes in “patchy”.

The second function, #feat(), creates a matrix given a set of features — this is the function used in a rewrite rule. Alpha notation requires a specific syntax: X + "feat" or X + [smallcaps("feat")]. A helper function #blank() adds a long underline for the context of application, and #a-r adds a right arrow (other arrows: #a-l, #a-u, #a-d, #a-lr, #a-ud, #a-sr, #a-sl, #a-r-large). Figure 9 shows a nasal place assimilation rule.

Figure 9: Nasal place assimilation.

SPE rewrite rule

#feat("+son", "–approx") #a-r
  #feat(sym.alpha + [#smallcaps("place")]) / #blank()\]#sub[#sym.sigma]
  #feat("–son", "–cont", "–del rel", sym.alpha + [#smallcaps("place")])

4 Prosody

Try on Snippyst

4.1 Sonority

When discussing the sonority principle in introductory courses, it is useful to illustrate relative sonority with a visual representation. The function #sonority(), based on the sonority scale in Parker (2011, 18), plots phonemes and their relative sonority profiles. If syllable boundaries are detected in the input, the function alternates between white and gray fills to distinguish each syllable.

Sonority profile

#sonority("vA \\*r .sI.ti", scale: 0.7)
Figure 10: Visualizing the sonority principle.

4.2 Syllables

Two options are available: #syllable() for a classic onset-rhyme representation, and #mora(..., coda: true) for a moraic representation. The latter allows you to define whether codas project a mora (coda: true). Both functions are used for single-syllable representations only and accept the same #ipa() input conventions.

Syllable (onset-rhyme)

#syllable("\\t tS \\ae t")

Syllable (moraic)

#mora("\\t tS \\ae t", coda: true)
Figure 11: Onset-rhyme.
Figure 12: Moraic.

Vowel length is represented in both #syllable() and #mora(): the : character triggers the length mark, and in moraic representations two moras branch out of the vowel.

Long vowel, onset-rhyme

#syllable("tR \\~ a:m")

Long vowel, moraic

#mora("tR \\~ a:m", coda: true)
Figure 13: Long vowel, onset-rhyme.
Figure 14: Long vowel, moraic.

The dimensions adjust as a function of how many segments are found in the input. Figure 15 illustrates this with an extreme example.

Figure 15: How spacing is managed in complex onsets and codas.

The scale argument takes care of both line width and text size uniformly. Figure 16, Figure 17, and Figure 18 show examples at different scale levels.

Figure 16: Scale 0.75.
Figure 17: Scale 0.5.
Figure 18: Scale 0.25.

4.3 Feet

TipCan I choose my own symbols?

Yes. If you prefer “Ft” instead of \(\Sigma\), for example, see the Symbols appendix.

The functions #foot() and #foot-mora() handle a single foot. A period . indicates syllabification and a single apostrophe ' marks the head of the foot, allowing easy creation of trochees and iambs. Non-binary feet (dactyls, etc.) are also supported.

Trochee

#foot("'p \\h \\ae.\\*r Is")

Iamb, moraic

#foot-mora("po.'Ral", coda: true)
Figure 19: Trochee.
Figure 20: Iamb, moraic.

Dactylic foot

#foot("'po.Ra.ma")

Dactylic foot, moraic

#foot-mora("'po.Ra.ma")
Figure 21: Dactylic foot.
Figure 22: Dactylic foot, moraic.

Geminates are also represented. In onset-rhyme representations, a geminate will be linked to the coda and the following onset. In moraic representations, coda: true provides the traditional representation.

Geminate, onset-rhyme

#foot("'pot.ta")

Geminate, moraic

#foot-mora("'pot.ta", coda: true)
Figure 23: Geminate, onset-rhyme.
Figure 24: Geminate, moraic.

The height of \(\Sigma\) is proportional to the width of the representation to avoid superposition of lines in extreme cases, as Figure 25 demonstrates.

Extreme foot

#foot("'xa.xa.xa.xa.xa.xa.xa.xa.xa.xa.xa.xa", scale: 0.7)
Figure 25: An extreme foot to stress test the function.

4.4 Prosodic words

Parentheses () define feet, so any syllable outside a foot is linked directly to the PWd. An apostrophe ' marks the head of each foot, and the argument foot: "R" or foot: "L" determines which foot carries primary stress in the word (when more than one foot is present).

Prosodic word (onset-rhyme)

#word("('po.Ra).ma")

Prosodic word (moraic)

#word-mora("('po.Ra).ma", coda: true)
Figure 26: PWd, onset-rhyme.
Figure 27: PWd, moraic.

Two-foot PWd, left head

#word-mora("('po.Ra).('ma.pa)", foot: "L")

Two-foot PWd, right head

#word("('po.Ra).('ma.pa)", foot: "R")
Figure 28: Two-foot PWd, left head.
Figure 29: Two-foot PWd, right head.

All lines are straight by design (no curved lines). In extreme scenarios — e.g., where an unfooted syllable is very far from the head foot — the height is adjusted to avoid line crossings, as Figure 30 demonstrates.

Extreme PWd

#word("xa.(xa.xa)(xa.xa)(xa.xa)(xa.xa)", scale: 0.7)
Figure 30: Unfooted syllable far from head foot.

4.5 Metrical grid

The function #met-grid() creates a metrical grid using \(\times\) to indicate prominence. The goal is high-quality output with minimal effort. Figure 31 and Figure 32 show grids for the word butterfly.

Metrical grid (string input)

#met-grid("bu3.tter1.fly2")

Metrical grid (tuple/IPA input)

#met-grid(("b2", 3), ("R \\schwar", 1), ("flaI", 2))
Figure 31: Metrical grid — string input.
Figure 32: Metrical grid — tuple/IPA input.

5 Autosegments

Try on Snippyst

The function #autoseg() allows you to represent either features or tones on a separate tier, including linking, delinking, floating tones, and contour tones. Inputs are arrays, so each phoneme is entered individually. This allows for empty spaces, domain boundary symbols, etc.

5.1 Feature spreading

Figure 33 demonstrates #autoseg() in a simple scenario of feature spreading. The argument links is an array of tuples: ((2,1),) means “draw a link from index 2 to index 1”. The argument arrow adds or removes arrow heads on linking lines.

Figure 33: Feature spreading, [+nas].

Nasal spreading

#autoseg(
    ("k", "\\ae", "n", "t"),
    features: ("", "", "[+nas]", ""),
    links: ((2, 1),),
    spacing: 1.0,
    arrow: false,
  )

Figure 34 shows an example of metaphony in Brazilian Veneto (Garcia and Guzzo 2023) where [+high] spreads from the final /i/ to /e/ (position 3) and /o/ (position 1). The argument gloss allows quick annotation.

Figure 34: Metaphony in Brazilian Veneto.

Metaphony (Brazilian Veneto)

#autoseg(
    ("z", "o", "v", "e", "n", "–", "i"),
    features: ("", "", "", "", "", "", "[+hi]"),
    links: ((6, 3), (6, 1)),
    spacing: 0.5,
    arrow: true,
    gloss: [_young_#smallcaps(".pl")],
  )

5.2 Tones

The argument tone: true “flips” the representation vertically to show tones above the segmental tier. Figure 35 shows low tone spreading without delinking in Nupe (Zsiga 2024).

Figure 35: Tone spreading in Nupe — two stages of the process side by side.

Tone spreading (Nupe)

#autoseg(
  ("e", "b", "e"),
  features: ("L", "", "H"),
  spacing: 0.5,
  tone: true,
  gloss: [],
)
#a-r  // arrow between stages
#autoseg(
  ("e", "b", "e"),
  features: ("L", "", "H"),
  links: ((0, 2),),
  spacing: 0.5,
  tone: true,
  gloss: [èbě _pumpkin_],
)

Floating tones can be added with the float argument. Figure 36 demonstrates how to add a floating tone and draw a circle around it with the highlight argument.

Figure 36: Floating tone with highlight.

Floating tone

#autoseg(
      ("i", "@", "N", "k", "a"),
      features: ("H", "", "L", "", "H"),
      highlight: (2,),
      spacing: 1.0,
      tone: true,
      float: (2,),
    )

Spreading from a contour tone requires a tuple inside features for the vowel linked to two tones. The links argument uses nested tuples: (((2, 0), 1),) means “from the first tone at position 2 (i.e., H), draw a dashed line to segment 1”. Figure 37 illustrates this.

Figure 37: Contour tone.

Contour tone

#autoseg(
  ("m", "1", "A", "u"),
  features: ("", "", ("H", "L"), ""),
  links: (((2, 0), 1),),
  tone: true,
  highlight: ((2, 0),),
  spacing: 1.0,
  arrow: true,
),

Figure 38 shows a three-stage OCP effect in Vai (Zsiga 2024), demonstrating delinking (delinks) and one-to-many tone relationships (multilinks). The argument delinks: ((3, 3),) states that the link between position 3 and itself must be delinked. The argument multilinks: ((6, (6, 9)),) means “center a tone on segments 6 and 9 simultaneously”.

Figure 38: OCP effects in Vai — delinking and one-to-many relationships.

OCP effects (Vai)

#autoseg(
      ("m", "u", "s", "u"),
      features: ("", "L", "", "H"),
      tone: true, spacing: 0.5, baseline: 37%,
      gloss: [_woman_],
    ) +
    #autoseg(
      ("k", "u", "n", "d", "u"),
      features: ("", "H", "", "", ""),
      tone: true,
      float: (1,),
      multilinks: ((1, (1, 4)),),
      spacing: 0.5, baseline: 37%,
      arrow: false,
      gloss: [_short_],
    ) #a-r
    #autoseg(
      ("m", "u", "s", "u", "–", "k", "u", "n", "d", "u"),
      features: ("", "L", "", "H", "", "", "H", "", "", ""),
      links: ((1, 3),),
      delinks: ((3, 3),),
      arrow: false,
      multilinks: ((6, (6, 9)),),
      tone: true,
      baseline: 37%,
      spacing: 0.50,
      gloss: [_short woman_],
    )

5.3 Prosody with ToBI

Try on Snippyst

Adding ToBI labels to strings can be achieved with the function #int(). The argument line: false turns off the vertical stem (e.g., for boundary tones). By default, the font size of tones is 0.8em.

Figure 39: Inline ToBI labels in running text.

Inline ToBI

You're a we#int("*L")rewolf?#h(2em)#int("H%", line: false)

I'm a wer#int("*H")ewolf.#h(2em)#int("L%", line: false)

Note that #int() is not meant to be used in numbered/unnumbered lists. In the vast majority of cases, strings with ToBI labels will appear in numbered examples — see the Numbered examples section.

5.4 Multi-tier

Try on Snippyst

The function #multi-tier() provides the freedom to create a wide range of non-linear structures that cannot be generated by single-purpose functions. The main argument is levels, an array of rows, where each row is an array of elements. Elements are automatically linked to the element on the level below; additional links can be specified with the links argument.

Figure 40 reproduces a figure from Booij (2012, 159) on the interface of phonology and morphology. Figure 41 shows the same figure with show-grid: true to help visualize positions.

Figure 40: Morphology and phonology, without grid.
Figure 41: The same figure with the grid visible.

Multi-tier (morphophonology)

#multi-tier(
  show-grid: true, // <- to help you see the grid
  levels: (
    ("", "", "", "", ("Adj", 3.5)),
    ("", "", "", "", ("Adj", 3.5)),
    ("", ("Af", 0.5), "", ("N", 2.5), "Af"),
    ("in", "ter", "na", "tion", "al"),
    ("sigma", "sigma", "sigma", "sigma", "sigma"),
    ("Sigma", "", "Sigma", "", ""),
    ("", "", "omega", "", ""),
  ),
  links: (
    ((0, 4), (2, 1)), // Adj -> Af
    ((1, 4), (2, 3)), // Adj -> N
    ((2, 1), (3, 0)), // Af -> in
    ((2, 3), (3, 2)), // N -> na
    ((5, 0), (4, 1)), // Ft -> Syl
    ((5, 2), (4, 3)), // Ft -> Syl
    ((6, 2), (5, 0)), // PWd -> Ft
    ((6, 2), (4, 4)), // PWd -> Ft
  ),
)

Elements specified as ("N", 2.5) are placed at fractional positions on the grid. The function also automatically detects Greek letters: adding "sigma" renders as \(\sigma\), "Sigma" as \(\Sigma\), "omega" as \(\omega\). Numbers in elements are automatically rendered as subscripts.

#multi-tier() can also create Government Phonology and CV Phonology representations. Figure 42, adapted from Goad (2012, 355), shows how arrows and arrow-delinks work. It is also possible to label tiers, as shown in Figure 43, adapted from Carvalho (2017).

Figure 42: Government Phonology.
Figure 43: CV Phonology with tier labels.

Government Phonology

#multi-tier(
  levels: (
    ("O", "R", "", "O", "R", "O", "R"),
    ("", "N1", "", "", "N2", "", "N3"),
    ("", "x", "x", "x", "x", "x", "x"),
    ("", "", "s", "t", "E", "m", ""),
  ),
  links: (((0, 1), (2, 2)),),
  ipa: (3,),
  arrows: (
    ((3, 3), (3, 2)),
    ((0, 4), (0, 1)),
  ),
  arrow-delinks: ((1,)),
  spacing: 1,
)

CV Phonology with tier labels

#multi-tier(
  levels: (
    ("T", "", "R", ""),
    ("O1", "n1", "O2", "n2"),
    ("x", "", ("x", 2.5), ""),
    ("o", "", ("N", 2.5), ""),
    ("", "", ("V", 2.5), ""),
  ),
  links: (((1, 3), (2, 2)),),
  dashed: (((2, 0), (3, 2)),),
  level-spacing: 1.2,
  highlight: ((1, 0),),
  spacing: 1,
  stroke-width: 0.7pt,
  tier-labels: (
    (1, "C-plane"),
    (2, "skeleton"),
    (3, "V-plane"),
  ),
  scale: 1,
)

6 Feature geometry

Feature geometry presents some important challenges, including the user interface: if too many degrees of freedom are available, a function becomes too convoluted. phonokit offers two dedicated functions: #geom() for single-segment trees and #geom-group() for multiple trees and processes.

6.1 Single trees

The arguments of #geom() are named after the nodes they add. If you add a node \(n\), the function automatically builds any parent node needed. For example, adding voice: true also adds [laryngeal]; adding anterior: true also adds [coronal]. Arguments accept both boolean and string values: nasal: true adds [nasal], while nasal: "+" adds [+nasal].

Figure 44 reproduces the full consonant representation from Clements and Hume (1995, 292).

Figure 44: Full consonant feature geometry.
Figure 45: Full vocoid feature geometry.

Full consonant feature geometry

#geom(
  root: ("±son", "±approx", "-vocoid"),
  spread: true,
  constricted: true,
  nasal: true,
  voice: true,
  labial: true,
  anterior: true,
  distributed: true,
  dorsal: true,
  continuant: true,
)

Full vocoid feature geometry

#geom(
  root: ("+son", "+approx", "+vocoid"),
  spread: true,
  constricted: true,
  nasal: true,
  vplace: true,
  aperture: (true, true, true),
  coronal: true,
  anterior: "-",
  distributed: true,
  dorsal: true,
  continuant: true,
  scale: 0.9,
)

Several phonemes come pre-built in #geom() via the ph argument, using the same #ipa() conventions. In the figures below, the only code needed is #geom(ph: "..."). You will also note timing units added by default (x). You can disable timing units by adding timing: false, or you can add morae instead (timing: "mora" or timing: "mu"). The timing argument also takes an array for long vowels: timing: ("x", "x"). For example, if you prefer morae to x slots, you’d add timing: ("mora", "mora") for the long vowel in Figure 47.

Figure 46: Preset /a/.
Figure 47: Preset /i:/.
Figure 48: Preset /k/.
Figure 49: Preset /tʃ/.

The ph argument offers minimal syntax, but you are not locked to presets. Any node can be overridden: for example, #geom(ph: "a", aperture: (true,)) overrides only the aperture specification for the preset /a/.

6.2 Multiple trees

The function #geom-group() works as a wrapper to “glue” multiple #geom() trees together, enabling arrows across trees. The key additional arguments are arrows, curved, and delinks.

Arrows reference nodes by name with a tree index: "nasal2" refers to [nasal] in tree 2, "root1" to the root in tree 1. The argument ctrl adjusts the control points of the Bezier curve (start and end directions). Figure 50 shows the spreading of [nasal].

Figure 50: Nasalization.
Figure 51: Metaphony with highlighted nodes and arrow.

Nasalization

#geom-group(
  (ph: "/a/"),
  (ph: "/n/"),
  arrows: (
    (from: "nasal2",
     to: "root1",
     ctrl: (1.1, -1.5)),
     ),
  curved: true,
  gap: 1,
)

By default, #geom() uses the Clements and Hume (1995) model. Setting model: "sagey" uses height features (Sagey 1986) instead of aperture. The argument highlight dims the entire representation and only highlights the specified nodes. Figure 51 illustrates metaphony where /e/ → [i] / __ C₀ /u/:

Metaphony (Sagey model)

#geom-group(
  (ph: "/e/"),
  (ph: "\\C"),
  (ph: "/u/", prefix: "-"),
  arrows: (
    (from: "high3", to: "dorsal1", ctrl: (2.3, -1.5)),
  ),
  delinks: ("high1",),
  highlight: ("high1", "dorsal1", "high3"),
  gap: 1.5,
  model: "sagey",
)

7 Optimality Theory

Try on Snippyst

The function #tableau() generates OT tableaux (Prince and Smolensky 1993). It takes six arguments: input, candidates, constraints, violations, winner, and dashed-lines. The violations argument requires a nested structure. The winner candidate counts from zero. Cells are automatically shaded after a fatal violation (!), and the pointing finger symbol ☞ is added for the winner.

Figure 52: A typical OT tableau.

OT tableau

#tableau(
  input: "kraTa",
  candidates: ("kra.Ta", "ka.Ta", "ka.ra.Ta"),
  constraints: ("Max", "Dep", "*Complex"),
  violations: (
    ("", "", "*"),
    ("*!", "", ""),
    ("", "*!", ""),
  ),
  winner: 0,        // position of winning candidate
  dashed-lines: (1,), // note the comma
  shade: true,      // true by default
  letters: true
)

One nice feature of #tableau() is that the function automatically shades cells once a fatal violation is entered (!). Likewise, it adds the “☞” symbol for the winner, whose position is extracted from the winner argument. Candidates can also be labeled with letters (a., b., c., …) by setting letters: true. When letters are used, the ☞ symbol is placed to the left of the winning candidate’s letter.

Additionally, #tableau() supports prosodic structures as candidates.1 You can pass prosodic function calls as content using square brackets, e.g., [#syllable("mat")]. This is the recommended approach because it avoids conflicts with the single quote character, which is also used for stress marking in prosodic notation. Figure 53 shows an example with #word() candidates. When passing content directly, you control the scale via the function’s own scale argument (this is key because prosodic structures as often too large for a tableau). Figure 53 also shows the gloss argument in case more information is needed for the input. This argument requires two strings (orthographic form and translation).

Figure 53: A tableau with prosodic representation.

OT tableau + prosody

#tableau(
    input: "prato",
    candidates: (
      [#word("('pra.to)", scale: 0.8)],
      [#word("('pa.to)", scale: 0.8)],
    ),
    constraints: ("Faith", "*Complex"),
    violations: (
      ("", "*!"),
      ("", ""),
    ),
    winner: 0,
    letters: true,
    gloss: ("prato", "plate"),
  )

Hasse diagrams for constraint rankings can be generated with the #hasse() function — see Figure 54 and Figure 55. The function takes tuples with \(n\) elements: a 2-element tuple ("C1", "C2") draws a ranking edge; a 1-element tuple ("C3",) creates a floating constraint. The third element in a tuple controls the “stratum” (vertical position). A fourth element can specify line type: "dashed" or "dotted".

Figure 54: Hasse diagram with dotted lines and small caps.
Figure 55: Complex Hasse diagram adapted from Lamont (2021).

Constraint names are automatically rendered in small caps (except features inside square brackets).

Hasse diagram with dotted lines

#hasse(
    (
      ("*Complex", "Max", 0),
      ("*Complex", "Dep", 0),
      ("Onset", "Max", 0),
      ("Onset", "Dep", 0),
      ("Max", "NoCoda", 1),
      ("Dep", "Constraint[Feat]", 1, "dotted"),
    ),
    node-spacing: 3,
  )

Complex Hasse diagram

#hasse(
    (
      ("Ident[F]", "Agree[place]", 0),
      ("Dep", "Agree[vce]", 0),
      ("Dep", "Agree[place]", 0),
      ("Max[nas]", "Ident[place]", 1),
      ("Max[cor]", "Ident[place]", 1),
      ("Max[cor]", "Ident[vce]", 1),
      ("Max[h]", "Ident[vce]", 1),
      ("Agree[place]", "Ident[place]", 1),
      ("Agree[vce]", "Ident[vce]", 1),
      ("Ident[place]", "*[labial]", 2),
      ("Ident[vce]", "*[+voice]", 2),
      ("*[labial]", "Max", 3),
      ("*[+voice]", "Max", 3),
    ),
  )

8 Maximum Entropy

The function #maxent() produces a MaxEnt tableau (Goldwater and Johnson 2003; Hayes and Wilson 2008). Given the constraint weights and violations, it automatically calculates \(h_i\), \(e^{-h_i}\), and \(P(y|x)\):

\[P(y|x) = \frac{e^{-\sum_{i=1}^n w_i C_i(y,x)}}{Z(x)}\]

The column \(h_i\) displays the Harmony score (weighted sum of violations); \(e^{-h_i}\) is the unnormalized probability (MaxEnt score); and \(P_i\) is the normalized predicted probability. The function also prints probability bars at the right margin by default (visualize: true, top and bottom in Figure 56). Candidates can be sorted from most to least probable with sort: true (bottom in Figure 56).

Figure 56: MaxEnt tableaux.

MaxEnt tableau

#maxent(
  input: "kraTa",
  candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"),
  constraints: ("Max", "Dep", "*Complex"),
  weights: (2.5, 1.8, 0.5),
  violations: (
    (0, 0, 1),
    (1, 0, 0),
    (0, 1, 0),
  ),
  visualize: true,  // show probability bars (default)
  sort: true        // sort candidates from most to least probable
)
TipHarmonic Grammars

phonokit also has functions for Harmonic Grammar (#hg()) and Noisy Harmonic Grammar (#nhg()). These functions share the same syntax as #maxent(). The #nhg() function derives probabilities by simulating evaluations (default: 1000) given the constraints and violations, and can display the noise column. These functions are based on conventions in Flemming (2021).

9 Numbered examples

The function #ex() creates numbered linguistic examples, accompanied by #subex-label() for labeling each line within an example. Because #ex() is based on tables, the user can customize it with however many columns are needed.

TipImportant

To use this function, add this line after importing the package: #show: ex-rules

Numbered example

// #import "@preview/phonokit:0.5.2": *
// #show: ex-rules  // <- add to your document
// ...
#ex(caption: "A phonology example",
    labels: (<ex-anba>, <ex-anka>),
    columns: (5em, 2em, 5em))[
      - #ipa("/anba/") & #a-r & #ipa("[amba]")
      - #ipa("/anka/") & #a-r & #ipa("[aNka]")
    ] <phon-ex>

ToBI numbered example

#ex(caption: "Some ToBI examples",
    title: [Autosegmental transcription of intonation in English @zsiga2013sounds],
    labels: (<ex-tobi1>, <ex-tobi2>),
    )[
      - You're a we#int("*L")rewolf?#h(1em)#int("H%", line: false)
      - I'm a wer#int("*H")ewolf.#h(1em)#int("L%", line: false)
    ] <tobi-ex>
Figure 57: Numbered linguistic example.
Figure 58: Numbered example with ToBI annotations.

The entire example can be referred to as @phon-ex, and sub-examples can be referred to by their labels (e.g., @ex-anba). Specifying exact column widths (columns: (2em, 2em, 5em, ...)) guarantees perfect alignment across different examples in the document. For ToBI strings, align: left + bottom is essential to ensure letters align with the text baseline rather than the tone annotations.

As of version 0.4.6, a title argument is also available for examples, displayed below the caption number. The caption argument in #ex() is not printed directly — it is there in case you wish to create a table of contents for examples using #outline(target: figure.where(kind: "linguistic-example")).


10 Appendix

10.1 How can I use Typst offline?

While Typst’s own online editor at Typst.app is very useful and practical, most of us prefer to work offline. How can you use Typst offline then?

One of the best IDE options out there is to use VS Code with the extension Tinymist Dreamin and Varner (2024) — the extension is therefore available for Positron, which is the successor to RStudio. Tinymist is also available as a plugin for NeoVim users. All these options work extremely well because Tinymist is great, and I haven’t had any issues thus far: compilation is instantaneous, and bib files also work flawlessly2

If you use Quarto, it is very easy to use phonokit with your qmd files. You need to first declare typst as your format. Then, import the package inside a typst code block and you’re done. Now you’ll be able to use any function you want. Just remember you need the {=typst} suffix every time (which you can automate with a simple snippet in Positron, RStudio, etc.; see here).

    ---
    title: "A Quarto document"
    format: typst
    ---

    ```{=typst}
    #import "@preview/phonokit:0.5.2": *
    ```

    Now you can use any function you want:

    `#syllable("pat"){=typst}`

10.2 Symbols in prosodic representations

As of version 0.3.7, users can decide which symbols are used for prosodic words, feet, syllables, and moras via the symbol argument, which requires an array. The default symbols are Greek letters: \(\omega\), \(\Sigma\), \(\sigma\), \(\mu\).

Figure 59: Prosodic word, custom symbols.
Figure 60: Foot, custom symbols.
Figure 61: Foot with moras, custom symbols.
Figure 62: Syllable, custom symbol.

Custom symbols

#word("@.( \\t dZ En).d@", symbol: ("1", "2", "3"))
#foot("\\t dZ En", symbol: ("1", "2"))
#foot-mora("\\t dZ En", symbol: ("1", "2", "3"))
#syllable("\\t dZ En", symbol: ("1",))

10.3 Exporting as images

To use phonokit without adopting Typst as your primary tool, you can export any representation as a PNG. First, create a Typst file with width: auto and height: auto in the page settings:

Typst page setup for export

#import "@preview/phonokit:0.4.0": *
#set page(width: auto, height: auto, margin: 0.5em, fill: none)
// ... your phonokit function here

Then compile from the terminal with:

Compile to PNG (bash)

typst compile file.typ file-{p}.png --ppi 500

This generates a PNG file with 500 pixels per inch and transparent background.

10.4 Extras: arrows and Greek letters

The extras.typ module provides convenience symbols.

Arrows:

Function Description
#a-r Right arrow
#a-l Left arrow
#a-u Up arrow
#a-d Down arrow
#a-lr Bidirectional arrow
#a-ud Vertical bidirectional arrow
#a-sr Squiggly right arrow
#a-sl Squiggly left arrow
#a-r-large Large right arrow with spacing

Greek letters (render upright, non-italicized):

Function Function Function
#alpha #mu #tau
#beta #phi #omega
#gamma #pi #cap-phi
#delta #sigma #cap-sigma
#lambda #cap-omega

Utilities:

  • #blank() — underline blank for fill-in exercises or SPE rules; width adjustable: #blank(width: 4em)
  • #extra[...] — wraps content in ⟨angle brackets⟩ for extrametricality

Copyright © Guilherme Duarte Garcia

References

Booij, Geert. 2012. The Grammar of Words: An Introduction to Linguistic Morphology. 3rd ed. Oxford University Press.
Carvalho, Joaquim Brandão de. 2017. “Deriving Sonority from the Structure, Not the Other Way Round: A Strict CV Approach to Consonant Clusters.” The Linguistic Review 34 (4): 589–614.
Chomsky, N., and M. Halle. 1968. The sound pattern of English. Harper & Row.
Clements, George N., and Elizabeth Hume. 1995. “The Internal Organization of Speech Sounds.” In The Handbook of Phonological Theory, edited by John A. Goldsmith. Blackwell.
Dreamin, Myriad, and Nathan Varner. 2024. Tinymist: An Integrated Language Service for Typst. Version 0.14.4. https://github.com/Myriad-Dreamin/tinymist.
Flemming, Edward. 2021. “Comparing MaxEnt and Noisy Harmonic Grammar.” Glossa: A Journal of General Linguistics 6 (1): 1–42. https://doi.org/10.16995/glossa.5775.
Garcia, Guilherme D, and Natália B. Guzzo. 2023. “A Corpus-Based Approach to Map Target Vowel Asymmetry in Brazilian Veneto Metaphony.” Italian Journal of Linguistics 35 (1): 115–38. https://doi.org/10.26346/1120-2726-205.
Goad, Heather. 2012. “SC Clusters Are (Almost Always) Coda-Initial.” Linguistic Review 29 (3).
Goldwater, Sharon, and Mark Johnson. 2003. “Learning OT Constraint Rankings Using a Maximum Entropy Model.” Proceedings of the Stockholm Workshop on Variation Within Optimality Theory, 111–20.
Hayes, Bruce. 2009. Introductory Phonology. John Wiley & Sons.
Hayes, Bruce, and Colin Wilson. 2008. “A Maximum Entropy Model of Phonotactics and Phonotactic Learning.” Linguistic Inquiry 39 (3): 379–440. https://doi.org/10.1162/ling.2008.39.3.379.
Lamont, Andrew. 2021. “Optimality Theory Implements Complex Functions with Simple Constraints.” Phonology 38 (4): 729–40. https://doi.org/10.1017/s0952675721000361.
Parker, Steve. 2011. “Sonority.” In The Blackwell Companion to Phonology, edited by Marc van Oostendorp, Colin J Ewen, Elizabeth Hume, and Karen Rice. Wiley Online Library. https://doi.org/10.1002/9781444335262.wbctp0049.
Prince, Alan, and Paul Smolensky. 1993. Optimality Theory: Constraint Interaction in Generative Grammar. Blackwell.
Rei, Fukui. 1996. TIPA: A System for Processing Phonetic Symbols in LATEX. TUGBoat.
Sagey, Elizabeth. 1986. “The Representation of Features and Relations in Nonlinear Phonology.” PhD thesis, MIT.
SIL International. 2025. Charis. Version 7. https://software.sil.org/charis/.
Zsiga, Elizabeth C. 2024. The Sounds of Language: An Introduction to Phonetics and Phonology. John Wiley & Sons.

Footnotes

  1. This also applies to other constraint-based functions in the package, discussed later in this manual.↩︎

  2. Provided that they are clean and do not have any problems regarding fields, repeated entries, etc.↩︎