typst-touying

star 0

Touying slide framework for Typst. Use when creating presentations in Typst, adding slide animations, configuring Touying themes, using pause/meanwhile markers, building multi-file presentations, or integrating CeTZ/Fletcher diagrams with animations. Covers all built-in themes (simple, university, metropolis, aqua, dewdrop, stargazer) and custom theme creation.

statzhero By statzhero schedule Updated 4/30/2026

name: typst-touying description: | Touying slide framework for Typst. Use when creating presentations in Typst, adding slide animations, configuring Touying themes, using pause/meanwhile markers, building multi-file presentations, or integrating CeTZ/Fletcher diagrams with animations. Covers all built-in themes (simple, university, metropolis, aqua, dewdrop, stargazer) and custom theme creation. license: CC-BY-4.0 metadata: author: Ulrich Atz

Touying Slide Framework

Based on Touying 0.7.3 for Typst

Touying is a presentation framework for Typst that provides content/style separation, fast compilation (milliseconds vs Beamer's seconds), and dynamic slide capabilities via #pause and #meanwhile markers.

Related skills:

  • /typst - Core Typst syntax, styling, math, tables, figures
  • /typst-cetz - Detailed CeTZ drawing API (coordinates, shapes, anchors, marks, trees, plots)

Presentation Design Principles

Typography: limit to 4-5 font sizes

A presentation should use at most 4-5 distinct font sizes, applied consistently within each slide. Draw from a LaTeX-inspired scale (adapt exact values to your design):

Role Approximate size LaTeX analogue
Presentation title 28-32pt \LARGE
Section divider 24-28pt \Large
Slide title 20-24pt \large
Body text 16-18pt \normalsize
Captions / footnotes 12-14pt \footnotesize

Define sizes once in your theme or preamble and reference them everywhere. Never introduce an ad-hoc size; if text needs emphasis, use weight or color, not a sixth size. Every element on a single slide should come from this scale—mixing arbitrary sizes looks unprofessional.

// Example: define your type scale once
#let title-size = 24pt
#let body-size  = 17pt
#let small-size = 13pt

#set text(size: body-size)

Slide titles: sentence case

All slide titles use sentence case (capitalize only the first word and proper nouns). The only exception is the presentation name on the title slide, which may use title case.

  • Good: == Results from the pilot study
  • Good: == How ESG scores affect returns
  • Bad: == Results From The Pilot Study

Color palette: restrained, intentional

Avoid the default "Python conference" color scheme (saturated blue-orange-green) and overuse of colored background tints. Prefer:

  • Neutral base: white or very light warm grey (#f5f5f5) backgrounds; dark grey (#333) or near-black text.
  • One accent color for emphasis (headings, highlights, links). A second accent is acceptable for contrast (e.g., positive/negative) but not required.
  • Grey for secondary elements: axis lines, borders, annotations, de-emphasized text. A medium grey (#888) or light grey (#ccc) works well.
  • Background tints: use sparingly. A subtle grey tint on a code block or callout is fine; avoid tinting every other slide or coloring full-bleed section dividers in saturated hues.
// Example: clean, restrained palette
config-colors(
  primary: rgb("#2c3e50"),         // dark blue-grey accent
  secondary: rgb("#7f8c8d"),       // medium grey
  neutral-lightest: rgb("#fafafa"),
  neutral-darkest: rgb("#333333"),
)

Quick Start

#import "@preview/touying:0.7.3": *
#import themes.simple: *

#show: simple-theme.with(aspect-ratio: "16-9")

= Section Title

== Slide Title

Content here.

#pause

More content revealed on next click.

Code Styles

Heading-Based (Simple Style)

Use standard Typst headings to create slides:

= Section Title     // Creates section divider slide
== Slide Title      // Creates content slide
=== Subsection      // Theme-dependent behavior

Content under headings becomes slide content.

#pause              // Progressive reveal

More content.

Special markers:

  • == <touying:hidden> - Creates slide without title
  • #pagebreak() or --- - Manual page break

Block Style (Explicit Functions)

For more control, use explicit slide functions:

#slide[
  Content here.

  #pause

  More content.
]

#focus-slide[
  Key message
]

#title-slide()

Block style enables:

  • Theme-specific slide variants
  • Callback-style animations with #only and #uncover
  • Custom parameters per slide

Animation Markers

Simple Animations

#pause - Reveals content progressively:

#slide[
  First item
  #pause
  Second item (appears on click)
  #pause
  Third item
]

Works inline: First #pause Second

#meanwhile - Shows content simultaneously across subslides:

#slide[
  Left column content
  #pause
  More left content

  #meanwhile

  Right column (shows with "Left column content")
  #pause
  More right (shows with "More left content")
]

Complex Animations

For advanced control, use callback-style functions:

#slide(repeat: 3)[
  #let (uncover, only, alternatives) = utils.methods(self)

  #only(1)[Only on subslide 1]
  #only("2-")[On subslides 2 and after]

  #uncover("2-3")[Visible on 2-3, space reserved otherwise]

  #alternatives[Option A][Option B][Option C]
]

Index syntax:

  • 1 - Specific subslide
  • "2-" - From subslide 2 onward
  • "2-3" - Subslides 2 through 3
  • "-3" - Up to subslide 3

Functions:

  • only(idx)[content] - Shows only on specified subslides, no space when hidden
  • uncover(idx)[content] - Shows on specified subslides, reserves space when hidden
  • alternatives[a][b][c] - Shows different content per subslide

Equation Animations

#touying-equation(`
  a + b &= c \
  #pause
  d + e &= f
`)

Item-by-Item Animation (0.6.3+)

Progressively reveal list items without manual #pause between each:

#slide[
  #item-by-item[
    - First point
    - Second point
    - Third point
  ]
]

Works with bullet lists, enumerations, and term lists.

Animated Code Reveals (0.6.3+)

#slide[
  #touying-raw(
    ```python
    def hello():
        print("Hello")
        #pause
        return True
    ```
  )
]

Jump Animation Control (0.6.3+)

Unified animation primitive (#pause and #meanwhile are syntax sugar for this):

#slide[
  Content on subslide 1
  #jump(2)           // skip to subslide 3
  Content on subslide 3
]

// Relative jumps
#jump(1, relative: true)  // advance by 1

Handout Controls (0.6.3+)

// Content only in handout mode
#handout-only[Extra details for handout]

// Control which subslide appears in handout
#slide(handout-subslides: 3)[
  // Handout shows subslide 3 state
]

Speaker Notes (0.7.3+)

Speaker notes now attach to the slide above them:

== My Slide

Content here.

#speaker-note[
  Remember to mention the key finding.
]

Slide Overflow (0.7.1+)

// Allow content to break across pages
#slide(breakable: true)[
  Very long content...
]

// Clip overflowing content
#slide(clip: true)[
  Content that might overflow...
]

Lazy Layout (0.7.1+)

Equalize heights/widths across columns:

#slide[
  #lazy-layout(
    columns: (1fr, 1fr),
    [Short content],
    [Much longer content that would normally make this column taller],
  )
]

// Shorthand aliases
#cols[Left][Right]           // alias for side-by-side (0.7.3+)
#lazy-h[Left][Right]        // equalized horizontal
#lazy-v[Top][Bottom]         // equalized vertical

Access Configuration (0.7.1+)

// Read current Touying config at runtime
#let cfg = touying-get-config()

Cover Customization

Default cover uses hide() (invisible but preserves layout).

// Semi-transparent cover (shows grayed content)
#show: simple-theme.with(
  config-methods(
    cover: utils.semi-transparent-cover.with(alpha: 85%)
  )
)

// For enum/list markers that hide() doesn't conceal
#show: simple-theme.with(
  config-methods(
    cover: (self: none, body) => box(scale(x: 0%, body))
  )
)

Themes

Simple Theme

#import "@preview/touying:0.7.3": *
#import themes.simple: *

#show: simple-theme.with(
  aspect-ratio: "16-9",        // or "4-3"
  footer: [Footer text],
  primary: rgb("#004488"),     // Theme color
)

#title-slide()
#centered-slide[Centered content]
#focus-slide[Key point]

University Theme

#import themes.university: *

#show: university-theme.with(
  aspect-ratio: "16-9",
  config-info(
    title: [Presentation Title],
    subtitle: [Subtitle],
    author: [Author Name],
    date: datetime.today(),
    institution: [Institution],
    logo: image("logo.png", width: 2cm),
  ),
  progress-bar: true,
)

#title-slide()
#matrix-slide[Column 1][Column 2]  // Multi-column
#focus-slide[Important point]

Colors: primary #04364A, secondary #176B87, tertiary #448C95

Metropolis Theme

#import themes.metropolis: *

#show: metropolis-theme.with(
  aspect-ratio: "16-9",
  footer-progress: true,
  config-info(
    title: [Title],
    author: [Author],
    institution: [Institution],
  ),
)

// Recommended fonts
#set text(font: "Fira Sans", weight: "light", size: 20pt)
#show math.equation: set text(font: "Fira Math")

#title-slide()
#new-section-slide[Section Name]
#focus-slide[Key point]

Colors: primary #eb811b (orange), secondary #23373b

Aqua Theme

#import themes.aqua: *

#show: aqua-theme.with(
  aspect-ratio: "16-9",
  config-info(title: [Title], author: [Author]),
)

#title-slide()
#outline-slide()
#focus-slide[Key message]

Colors: primary #003F88

Dewdrop Theme

Two navigation modes: sidebar or mini-slides.

#import themes.dewdrop: *

#show: dewdrop-theme.with(
  aspect-ratio: "16-9",
  navigation: "sidebar",        // or "mini-slides" or none
  config-info(
    title: [Title],
    author: [Author],
  ),
)

#title-slide()
#focus-slide[Key point]

Stargazer Theme

#import themes.stargazer: *

#show: stargazer-theme.with(
  aspect-ratio: "16-9",
  config-info(
    title: [Title],
    author: [Author],
    date: datetime.today(),
    institution: [Institution],
  ),
)

#title-slide()
#outline-slide()
#focus-slide[Key point]

Colors: primary #005bac

Global Settings

Presentation Info

#show: simple-theme.with(
  config-info(
    title: [Presentation Title],
    subtitle: [Subtitle],
    author: [Author Name],
    date: datetime.today(),
    institution: [Institution],
    logo: image("logo.png"),
  ),
)

Access in templates: self.info.title, self.info.author, etc.

Date Formatting

#show: simple-theme.with(
  config-common(datetime-format: "[year]-[month]-[day]"),
)

Handout Mode

Disables animations, shows final state of each slide:

#show: simple-theme.with(
  config-common(handout: true),
)

Page Layout

Warning: Do not use set page(..) directly; Touying resets it. Use config-page() instead.

Configure Page

#show: simple-theme.with(
  config-page(
    margin: (x: 4em, y: 2em),
    header: align(top)[Header],
    footer: align(bottom)[Footer],
    header-ascent: 0em,
    footer-descent: 0em,
  ),
)

Multi-Column Slides

#slide(composer: (1fr, 1fr))[
  Left column content
][
  Right column content
]

// Custom proportions
#slide(composer: (2fr, 1fr))[
  Wider left
][
  Narrower right
]

Sections and Outline

Heading Levels

Default mapping (theme-dependent):

  • = Section - Section divider
  • == Title - Slide title
  • === Subsection - Varies by theme

Configure with slide-level in config-common.

Section Numbering

#set heading(numbering: "1.1")
#show heading.where(level: 1): set heading(numbering: "1.")

Table of Contents

#slide[
  #outline()
]

// Auto-fitting columns
#slide[
  #adaptive-columns()[
    #outline()
  ]
]

// Progressive outline (dewdrop theme)
#progressive-outline()

Multi-File Organization

globals.typ - Shared imports and configuration:

#import "@preview/touying:0.7.3": *
#import themes.university: *

#let my-theme = university-theme.with(
  config-info(
    title: [My Presentation],
    author: [Author],
  ),
)

main.typ - Entry point:

#import "globals.typ": *

#show: my-theme

#include "sections/intro.typ"
#include "sections/methods.typ"
#include "sections/conclusion.typ"

sections/intro.typ - Content file:

#import "../globals.typ": *

= Introduction

== Background

Content here.

Utilities

Fit to Height/Width

// Fill remaining vertical space
#utils.fit-to-height(1fr)[
  #image("large-image.png")
]

// Fill horizontal space
#utils.fit-to-width(1fr)[
  #lorem(50)
]

Parameters:

  • grow: true/false - Allow expansion
  • shrink: true/false - Allow reduction
  • prescale-width - Assumed container width before scaling

Counters

// Current slide number
#utils.slide-counter.display()

// Progress bar
#utils.touying-progress(ratio => {
  box(width: ratio * 100%, height: 2pt, fill: primary)
})

Appendix

#show: appendix

= Appendix

== Backup Slides

// Use <touying:unoutlined> to hide from outline

CeTZ Integration

Animate CeTZ diagrams with touying-reducer. See /typst-cetz skill for full CeTZ API documentation.

#import "@preview/cetz:0.5.0"

#let cetz-canvas = touying-reducer.with(
  reduce: cetz.canvas,
  cover: cetz.draw.hide.with(bounds: true)
)

#slide[
  #cetz-canvas({
    import cetz.draw: *
    rect((0, 0), (2, 2))
    (pause,)
    circle((3, 1), radius: 0.5)
    (pause,)
    line((2, 1), (2.5, 1))
  })
]

For advanced animations, use only and uncover within the canvas by updating the cover method on self.

Fletcher Integration

Animate Fletcher diagrams:

#import "@preview/fletcher:0.5.8"

#let fletcher-diagram = touying-reducer.with(
  reduce: fletcher.diagram,
  cover: fletcher.hide
)

#slide[
  #fletcher-diagram(
    node((0, 0), [Start]),
    (pause,)
    edge("->"),
    node((1, 0), [End]),
  )
]

Building Custom Themes

#let my-theme(
  aspect-ratio: "16-9",
  ..args,
  body,
) = {
  show: touying-slides.with(
    config-page(..utils.page-args-from-aspect-ratio(aspect-ratio)),
    config-colors(
      primary: rgb("#004488"),
      secondary: rgb("#88aa00"),
      neutral-lightest: white,
      neutral-darkest: black,
    ),
    config-store(title: none),
    config-common(slide-fn: slide),
    ..args,
  )

  body
}

#let slide(title: auto, ..args) = touying-slide-wrapper(self => {
  // Custom slide implementation
  touying-slide(
    self: self,
    ..args,
  )
})

#let title-slide() = touying-slide-wrapper(self => {
  // Custom title slide
})

#let focus-slide(body) = touying-slide-wrapper(self => {
  // Custom focus slide
})

Common Patterns

Alert Text

#show strong: alert  // Makes **text** use primary color

This is **important** text.

Disable Section Slides

#show: simple-theme.with(
  config-common(new-section-slide-fn: none),
)

Custom Header/Footer

#show: simple-theme.with(
  header: self => self.info.title,
  footer: self => [#self.info.author #h(1fr) #utils.slide-counter.display()],
)

Resources

Install via CLI
npx skills add https://github.com/statzhero/typst-touying-skill --skill typst-touying
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator