1. Introduction
  2. User Guide
  3. 1. Installation
  4. 2. Quick Start
  5. 3. Changelog
  6. Usage
  7. 4. Command Line Interface
  8. 5. Editor Integration
  9. Features
  10. 6. Formatting Features
    1. 6.1. Markup
    2. 6.2. Code
    3. 6.3. Math Equations
    4. 6.4. Tables
  11. 7. Escape Hatch
  12. 8. Limitations
  13. Advanced
  14. 9. How It Works
  15. 10. Developer Guide
    1. 10.1. Core
    2. 10.2. Documentation
    3. 10.3. Playground

typstyle

#

Code Formatting

#

Code Block Structure

#

Single-Statement Blocks

Single-statement blocks remain inline when they fit within the line width, unless they are too long or have multiline flavor:

Example max_width: 40
Before
#let x = if true { 1 } else { 2 }
#let x = if true {
  1 } else { 2 }
#let x = if true {
  1 } else {
     2 }
#let x = if true { "111111111111" } else { "222222222222222222222222222222" }
After
#let x = if true { 1 } else { 2 }
#let x = if true {
  1
} else { 2 }
#let x = if true {
  1
} else {
  2
}
#let x = if true {
  "111111111111"
} else {
  "222222222222222222222222222222"
}

#

Linebreak Management

typstyle strips excessive newlines in code blocks:

Example
Before
#{


  let x = 1

  let y = 2


}
After
#{
  let x = 1

  let y = 2
}

#

Content Blocks

When content blocks have leading or trailing spaces, they break into multiple lines for better readability:

Example
Before
#{
  let res = if true [ The Result is definitely true. ]else[ false. ]
}
After
#{
  let res = if true [
    The Result is definitely true.
  ] else [ false. ]
}

#

Parentheses Removal

typstyle removes unnecessary parentheses around:

  • Literals
  • Arrays
  • Dictionaries
  • Destructuring patterns
  • Blocks
  • Patterns
Example
Before
#let  (( (( ((a)),))) ) =((( ( (1)),)) )
#let  (((( (( (a)),)) ))) =((((( (1)),)) ))

#let a = ((b:((c : ((3))))))
#let a = ({(true)})
#let a = (([()]))
After
#let ((a),) = (1,)
#let ((a),) = (1,)

#let a = (b: (c: 3))
#let a = { true }
#let a = [()]

Parentheses around identifiers are preserved for safety to avoid changing semantics.

Example
Before
#let name = "naming";
#let a = (name: 1)
#let b = ((name): 1)
#let c = (((name)): 1)
#let d = ("name": 1)
#let e = (("name"): 1)
After
#let name = "naming";
#let a = (name: 1)
#let b = ((name): 1)
#let c = ((name): 1)
#let d = ("name": 1)
#let e = ("name": 1)

#

Function Calls and Arguments

#

Argument Formatting

Function call arguments are intelligently spaced and aligned. typstyle handles various argument patterns including trailing commas and complex nesting:

Example
Before
#figure(caption: [A very long caption that exceeds the line width],placement:top,supplement:[Figure])
After
#figure(
  caption: [A very long caption that exceeds the line width],
  placement: top,
  supplement: [Figure],
)

#

Flavor Detection

typstyle uses "flavor detection" to determine formatting style. If the first space in arguments contains a newline, arguments are spread across multiple lines:

Example
Before
#let my-f(arg1,
 arg2,
  args: none) = {
}
#let my-f(arg1, arg2,
  args: none) = {
}
After
#let my-f(
  arg1,
  arg2,
  args: none,
) = {}
#let my-f(arg1, arg2, args: none) = {}

#

Combinable Arguments

typstyle uses a compact layout when the last argument is combinable and certain conditions are met:

Compact Layout Conditions:

  • No comments or intentional line breaks in arguments
  • Last argument is combinable (see types below)
  • Initial arguments can be flattened on one line
  • No blocky expressions in initial arguments
  • Same structured data type (array/dictionary) doesn't appear in earlier arguments

Combinable Expression Types:

  • Blocky expressions: code blocks, conditionals, loops, context expressions, closures
  • Structured data: arrays, dictionaries
  • Nested calls: function calls, parenthesized expressions
  • Content blocks: markup content in square brackets

When conditions are met, initial arguments are placed on the first line while the last combinable argument spans multiple lines without extra indentation.

Example
Before
#f(   if true {    let x = 3  })
#f(if true {
    let x = 3
  })
#f(    1111,    22222,    if true {
      let x = 3
      let y = 4
    },
  )
#f(1111,if true {let x = 3},22222, )
  #f(1111,if true {let x = 3 ;  let y = 4},22222, )
  #f(
context {
      1
    }
  )
After
#f(if true { let x = 3 })
#f(if true {
  let x = 3
})
#f(1111, 22222, if true {
  let x = 3
  let y = 4
})
#f(1111, if true { let x = 3 }, 22222)
#f(
  1111,
  if true {
    let x = 3
    let y = 4
  },
  22222,
)
#f(context {
  1
})
Example
Before
#f(
  (x: 1, y: 2), (a: 3, b: 4), (m: 5, n: 6),)
#f((x: 1, y: 2), (a: 3, b: 4), (m: 5, n: 6),)

#f(xx: 1, 2, 3, yyy: [
  Multiple line
  content in array
])

#f("string", aaa: (1, 2), bbb : (x: 1, y: 2), {
  x + y
})
After
#f(
  (x: 1, y: 2),
  (a: 3, b: 4),
  (m: 5, n: 6),
)
#f((x: 1, y: 2), (a: 3, b: 4), (m: 5, n: 6))

#f(xx: 1, 2, 3, yyy: [
  Multiple line
  content in array
])

#f("string", aaa: (1, 2), bbb: (x: 1, y: 2), {
  x + y
})
Example
Before
#set page(
  margin: 0.5in,
 footer: context {
  if counter(page).display() == "2" {
    [test]
  } else {
    []
  }
})

#assert.eq(parse(("asd",)), (description: "asd", types: none))
After
#set page(margin: 0.5in, footer: context {
  if counter(page).display() == "2" {
    [test]
  } else {
    []
  }
})

#assert.eq(parse(("asd",)), (
  description: "asd",
  types: none,
))

#

Chainable Expressions

#

Binary Chains

Binary expressions are formatted as operator chains with proper breaking and alignment:

Example max_width: 40
Before
#let _is_block(e,fn)=fn==heading or (fn==math.equation and e.block) or (fn==raw and e.has("block") and e.block) or fn==figure or fn==block or fn==list.item or fn==enum.item or fn==table or fn==grid or fn==align or (fn==quote and e.has("block") and e.block)
After
#let _is_block(e, fn) = (
  fn == heading
    or (fn == math.equation and e.block)
    or (
      fn == raw
        and e.has("block")
        and e.block
    )
    or fn == figure
    or fn == block
    or fn == list.item
    or fn == enum.item
    or fn == table
    or fn == grid
    or fn == align
    or (
      fn == quote
        and e.has("block")
        and e.block
    )
)

#

Dot Chains

Dot chains are broken intelligently based on complexity and function calls:

Example
Before
// Simple chains stay inline
#node.pos.xyz

// Complex chains with multiple calls break
#{let hlines_below_header = first-row-group-long-long.row_group-long-long-long-long.hlines-long-long-long-long}

#{
  let (title, _) = query(heading.where(level: 1)).map(e => (e.body, e.location().page())).rev().find(((_, v)) => v <= page)
}

#{padding.pairs().map((k, x) => (k, x * 1.5)).to-dict()}
After
// Simple chains stay inline
#node.pos.xyz

// Complex chains with multiple calls break
#{
  let hlines_below_header = first-row-group-long-long
    .row_group-long-long-long-long
    .hlines-long-long-long-long
}

#{
  let (title, _) = query(heading.where(level: 1))
    .map(e => (e.body, e.location().page()))
    .rev()
    .find(((_, v)) => v <= page)
}

#{
  padding
    .pairs()
    .map((k, x) => (k, x * 1.5))
    .to-dict()
}

#

Import Statements

#

Soft Wrapping

Import statements use soft wrapping for long item lists, keeping them compact yet readable:

Example
Before
#import "module.typ": very,long,list,of,imported,items,that,exceeds,line,width,and_,continues,wrapping

#import "@preview/fletcher:0.5.7" as fletcher:diagram,node,edge
After
#import "module.typ": (
  and_, continues, exceeds, imported, items, line,
  list, long, of, that, very, width, wrapping,
)

#import "@preview/fletcher:0.5.7" as fletcher: (
  diagram, edge, node,
)

#

Item Ordering

When enabled with --reorder-import-items (default), import items are sorted alphabetically:

Example
Before
#import "module.typ": zebra,alpha,beta,gamma
After
#import "module.typ": alpha, beta, gamma, zebra