The parize package provides an experimental feature that allows any block-level element in Typst to be treated as part of a paragraph.
Example One:

Code:
#import "@preview/parize:0.2.0": *
#set page(margin: 1cm, height: auto, width: 25cm)
#set par(justify: true)
#table(
columns: (1fr, 1fr),
[
#import "@preview/zebraw:0.6.3": *
#show: zebraw
```typst
#import "@preview/parize:0.2.0": *
#set par(first-line-indent: (amount: 2em, all: true))
#show strong: set text(fill: red, size: 1.2em) // debug
#set block(stroke: red) // debug
#show: par-indent.with(
include-elem: (heading, math.equation, list, enum, terms, ), // Enable paragraph indentation control for heading, math.equation, list, enum and terms
use-par-leading: true, // Enable paragraph spacing control for list, enum and terms
)
#set heading(numbering: "1.")
= Heading
*Indented* #lorem(10)
_Equation Test_. #lorem(8)
$
a^2 + b^2 = c^2
$
*Unindented* #lorem(10) // This paragraph remains unindented (no parbreak before equation)
#lorem(2)
$
a^2 + b^2 = c^2
$
*Indented* #lorem(10)
_Tight Lists Test_. #lorem(10)
+ #lorem(2)
+ #lorem(2)
*Unindented and par-leading* #lorem(8) // Unindented with paragraph-leading spacing
= Heading
*Unindented* #lorem(10)
+ #lorem(2)
+ #lorem(2)
*Indented* #lorem(10)
_Non-tight Lists Test_. #lorem(2)
- #lorem(2)
- #lorem(2)
*Unindented* #lorem(2) // Paragraph following lists without empty line
```
],
[
#show strong: set text(fill: red, size: 1.2em) // debug
#set block(stroke: red) // debug
#set par(first-line-indent: (amount: 2em, all: true))
#show: par-indent.with(
include-elem: (heading, math.equation, list, enum, terms), // Enable paragraph indentation control for heading, math.equation, list, enum and terms
use-par-leading: true, // Enable paragraph spacing control for list, enum and terms
)
#set heading(numbering: "1.")
= Heading
*Indented* #lorem(10)
_Equation Test_. #lorem(8)
$
a^2 + b^2 = c^2
$
*Unindented* #lorem(10) // This paragraph remains unindented (no parbreak before equation)
#lorem(2)
$
a^2 + b^2 = c^2
$
*Indented* #lorem(10)
_Tight Lists Test_. #lorem(10)
+ #lorem(2)
+ #lorem(2)
*Unindented and par-leading* #lorem(8) // Unindented with paragraph-leading spacing
= Heading
*Unindented* #lorem(10)
+ #lorem(2)
+ #lorem(2)
*Indented* #lorem(10)
_Non-tight Lists Test_. #lorem(2)
- #lorem(2)
- #lorem(2)
*Unindented* #lorem(2) // Paragraph following lists without empty line
],
)
Example Two:

Code:
#import "@preview/parize:0.2.0": *
#set page(margin: 1cm, height: auto, width: 25cm)
#table(
columns: (1fr, 1fr),
[
#import "@preview/zebraw:0.6.3": *
#show: zebraw
```typst
#import "@preview/parize:0.2.0": *
#set par(first-line-indent: (amount: 2em, all: true))
#show strong: set text(fill: red, size: 1.2em) // debug
#set block(stroke: red) // debug
#show: par-indent.with(
include-elem: (), // Enable paragraph indentation control for all block-level elements
use-par-leading: (
text-block-leading: (math.equation,),
block-text-leading: (list, enum, terms, math.equation),
),
)
_Equation Test_. #lorem(8) *par.leading* // no parbreak after
$
a^2 + b^2 = c^2
$
*Unindented + par.leading* #lorem(10) // Paragraph following equation without empty line
_Equation Test_. #lorem(8) *par.spacing*
$
a^2 + b^2 = c^2
$
*Indented + par.spacing* #lorem(10)
```
],
[
#set par(first-line-indent: (amount: 2em, all: true))
#show strong: set text(fill: red, size: 1.2em) // debug
#set block(stroke: red) // debug
#show: par-indent.with(
use-par-leading: (
text-block-leading: (math.equation,),
block-text-leading: (list, enum, terms, math.equation),
),
)
_Equation Test_. #lorem(8) *par.leading* // no parbreak after
$
a^2 + b^2 = c^2
$
*Unindented + par.leading* #lorem(10) // Paragraph following equation without empty line
_Equation Test_. #lorem(8) *par.spacing*
$
a^2 + b^2 = c^2
$
*Indented + par.spacing* #lorem(10)
],
)
- See rect-test for details on using
par-indentwith therectelement whenParagraph IndentationorParagraph Spacingis enabled. - See also inlinable-block.pdf (source) for details on using
par-indentwith custom block-level containers that can be treated as part of a paragraph.
Features
-
Paragraph Indentation: If a paragraph follows a block-level element without an empty line (i.e., no
parbreak()) between them, the paragraph will not be indented. Otherwise, it will be indented according to thepar.first-line-indentsetting.Example:

#import "@preview/parize:0.2.0": par-indent #set page(width: 12cm, margin: 1cm, height: auto) #show: par-indent.with(include-elem: (list, enum, terms, math.equation)) #let test-unindent = [ #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) unindent #lorem(2) $ a^2 + b^2 = c^2 $ #lorem(2) unindent #lorem(2) ] #let test-indent = [ #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) indent #lorem(2) $ a^2 + b^2 = c^2 $ #lorem(2) indent #lorem(2) ] #table( columns: (1fr, 1fr), [ #set par(first-line-indent: (amount: 2em, all: true)) #test-unindent #line(length: 100% + 10pt, start: (-5pt, 0pt)) #set par(first-line-indent: (amount: 2em, all: false)) #test-unindent ], [ #set par(first-line-indent: (amount: 2em, all: true)) #test-indent #line(length: 100% + 10pt, start: (-5pt, 0pt)) #set par(first-line-indent: (amount: 2em, all: false)) #test-indent ], ) -
Paragraph Spacing: If there is no empty line between a paragraph (or block-level element) and a block-level element,
parizeallows usingpar.leadingto control the spacing between them.Example:

#import "@preview/parize:0.2.0": par-indent #set page(width: 15cm, margin: 1cm, height: auto) #show: par-indent.with( include-elem: (list, enum, terms, math.equation), use-par-leading: true, ) #table( columns: (1fr,) * 3, [ #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) - #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) ], [ #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) - #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) ], [ #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) - #lorem(2) + #lorem(2) + #lorem(2) #lorem(2) ], )
Why This Package?
-
Experimental Solution: The
parizepackage serves as an experimental implementation of the concept discussed in issue #3206, offering more flexible control over block-level elements and exploring how this paragraph model affects Typst typesetting. -
Native Typst Limitations: Typst’s native
set par(first-line-indent: (amount: 2em, all: true))can be insufficient in certain scenarios. For instance, in theorem environments, lists, enums, and similar contexts, we often want the first line unindented while allowing indentation in subsequent paragraphs.parizeprovides a cleaner solution.
#set par(first-line-indent: 2em)
#show: par-indent.with(exclude-elem: (/*excludes specific block-level elements*/))
This approach ensures that the first line of paragraphs within a container remains unindented, while subsequent paragraphs (containing parbreak()) are indented according to user expectations. It also accommodates different regional typesetting conventions.
#import "@preview/parize:0.2.0": par-indent, parize-par-above-flag
#set par(first-line-indent: (amount: 2em, all: false))
#show: par-indent
#set page(width: 12cm, margin: 1cm, height: auto)
#set block(stroke: red) // debug
= Heading
Paragraph content // indented
= Heading
Paragraph content // unindented
#block([
#lorem(12) // unindented (for lists, theorem environments, etc.)
#lorem(5)
])
// In slide presentations, such as proof steps across multiple slides,
// you might want the first slide's paragraph unindented but subsequent ones indented:
#block([
#parize-par-above-flag
Proof. #lorem(12) // unindented
#lorem(5)
])
#block([
#parize-par-above-flag
Following #lorem(12) // indented
#lorem(5)
])

Example with Touying:
In touying, you can define a custom par-indent-slide method:
#import "@preview/touying:0.7.3": *
#import themes.university: *
#import "@preview/parize:0.2.0": par-indent, parize-par-above-flag
#show: university-theme.with(config-page(margin: 1cm, width: 15cm, height: auto))
#set par(first-line-indent: (amount: 2em, all: false))
#let par-indent-slide(body, ..args) = {
slide(
{
show: par-indent
parize-par-above-flag
body
},
..args,
)
}
#par-indent-slide[
*Proof*. #lorem(6) // unindented
StepI. #lorem(5) .... // indented
]
#par-indent-slide[
// Note: a `parbreak()` is required here
StepN. #lorem(5) .... // indented
Final. #lorem(5) .... // indented
]

Theorem Example:

#import "@preview/parize:0.2.0": par-indent, parize-par-above-flag
#set page(width: 12cm, margin: 1cm, height: auto)
#set par(first-line-indent: (amount: 2em, all: true), spacing: 1.5em)
#[
#set block(stroke: red) // debug
#show: par-indent
#let theorem(doc) = {
let number = context counter(figure.where(kind: "thm")).display("1.")
figure(
kind: "thm",
supplement: "Theorem",
block(
stroke: (left: 4pt + blue, rest: 1pt + blue),
inset: 5pt,
width: 100%,
{
parize-par-above-flag // `parize-par-above-flag` is used to prevent the first paragraph from being indented.
set align(left)
[
#set text(size: 1.5em, fill: blue)
#strong[Theorem #number]
]
h(.65em, weak: true)
doc
},
),
)
}
#theorem[
#lorem(10)
$
a^2 + b^2 = c^2
$
#lorem(2)
]
#lorem(2) unindented // Here, paragraph is not indented due to `par-indent` applying to the element `figure`.
#theorem[
#lorem(2)
$
a^2 + b^2 = c^2
$
#lorem(2)
]
#lorem(2) indented
]
More Remarks:
While context h(-par.first-line-indent.amount) might seem like an alternative, it is not always correct. For example:
#set par(first-line-indent: (amount: 2em, all: true))
#set page(width: 15cm, margin: 1cm, height: auto)
= use `#context (h(-par.first-line-indent.amount))`
#block(stroke: red)[
#lorem(5)
#[
#set text(size: 1.5em)
#rect()[]
#context (h(-par.first-line-indent.amount))#lorem(5)
]
#lorem(6)
]
#block(stroke: blue)[
#lorem(5)
#[
#set text(size: 1.5em)
#rect()[]
#context (h(-par.first-line-indent.amount))#lorem(5)
]
#lorem(6)
]
= use `par-indent`
#import "@preview/parize:0.2.0": *
#show: par-indent
#block(stroke: red)[
#lorem(5)
#[
#set text(size: 1.5em)
#rect()[]
#lorem(5)
]
#lorem(6)
]
#block(stroke: blue)[
#lorem(5)
#[
#set text(size: 1.5em)
#rect()[]
#lorem(5)
]
#lorem(6)
]

Usage
Import the parize package:
#import "@preview/parize:0.2.0": *
Then apply the par-indent method like:
#show: par-indent.with(
include-elem: (list, enum, terms, math.equation),
use-par-leading: true
)
Parameters
exclude-elemandinclude-elem:array- Default:
()
- Default:
use-par-leading:boolordictionary- Default:
false
- Default:
Paragraph Indentation
The include-elem parameter specifies which block-level elements should trigger indentation in following paragraphs: if no empty line (i.e., no parbreak()) separates them, the text remains unindented; otherwise, it follows the par.first-line-indent setting.
When include-elem is (), all block-level elements are processed (excluding par, align, and v to maintain compatibility with Typst’s native paragraph model). You can then use exclude-elem to exclude specific elements.
Default values for include-elem and exclude-elem are () (processing all block-level elements).
Supported block-level elements:
block,pad,figure,layoutlist,enum,termsheading,title,outline,repeattable,grid,stack,columnsmove,rotate,scale,skewcircle,ellipse,rect,squarecurve,image,line,polygonmath.equation,raw,quote
Example:

#import "@preview/parize:0.2.0": par-indent
#set page(width: 12cm, margin: 1cm, height: auto)
#set par(first-line-indent: (amount: 2em, all: true))
#set block(stroke: red) // debug
#[
#show: par-indent.with(exclude-elem: (heading, table))
exclude-elem: `heading`, `table`
#table(
columns: 2,
[
= heading
#lorem(2) indented
+ #lorem(2)
+ #lorem(2)
#lorem(2) unindented
#table(
columns: 2,
[1], [2],
)
#lorem(2) indented
#block()[#lorem(2)]
#lorem(2) unindented
],
[
= heading
#lorem(2) indented
+ #lorem(2)
+ #lorem(2)
#lorem(2) indented
#table(
columns: 2,
[1], [2],
)
#lorem(2) indented
#block()[#lorem(2)]
#lorem(2) indented
],
)
]
#[
#show: par-indent.with(include-elem: (heading, table))
include-elem: `heading`, `table`
#table(
columns: 2,
[
= heading
#lorem(2) unindented
+ #lorem(2)
+ #lorem(2)
#lorem(2) indented
#table(
columns: 2,
[1], [2],
)
#lorem(2) unindented
#block()[#lorem(2)]
#lorem(2) indented
],
[
= heading
#lorem(2) indented
+ #lorem(2)
+ #lorem(2)
#lorem(2) indented
#table(
columns: 2,
[1], [2],
)
#lorem(2) indented
#block()[#lorem(2)]
#lorem(2) indented
],
)
]
Paragraph Spacing
The use-par-leading parameter configures whether par.leading controls spacing when no empty line exists between a paragraph (or block-level element) and a block-level element, similar to how lists (list, enum, terms) behave in Typst.
Example: When use-par-leading is true, elements list, enum, terms are processed. If a paragraph follows tight lists without an intervening empty line (parbreak()), the spacing between them becomes par.leading.

Code:
#import "@preview/parize:0.2.0": par-indent
#set page(width: 15.5cm, margin: 1cm, height: auto)
#set par(first-line-indent: (amount: 2em, all: true), spacing: 1.5em)
#show enum: set block(stroke: red) // debug
#show: par-indent.with(use-par-leading: true)
#table(
columns: 4,
[
#lorem(2)
+ #lorem(2)
+ #lorem(2)
#lorem(2)
],
[
#lorem(2)
+ #lorem(2)
+ #lorem(2)
#lorem(2)
],
[
#lorem(2)
+ #lorem(2)
+ #lorem(2)
#lorem(2)
],
[
#lorem(2)
+ #lorem(2)
+ #lorem(2)
#lorem(2)
],
)
The use-par-leading parameter accepts a dictionary with the following keys:
apply-elem: specifies which block-level elements to process, affectingblock-text-leading,text-block-leading, andblock-block-leadingblock-text-leading: specifies which block-level elements to process when there’s no empty line between them and the following paragraphtext-block-leading: specifies which block-level elements to process when there’s no empty line between them and the preceding paragraphblock-block-leading: specifies which block-level elements to process when there’s no empty line between them and above block-level elements (excludingpar,align,v)
Values for these keys can be:
arraywhose elements are the following block-level elements:figure,list,enum,termsheading,title,outline,repeattable,columnsmove,rotate,scale,skewcircle,ellipse,rect,squarecurve,image,line,polygonmath.equation,raw,quoteblock(forparize’sparize-block)
"all": applies to all block-level elements listed above
Default: false (feature disabled). Setting use-par-leading: true is equivalent to use-par-leading: (block-text-leading: (list, enum, terms)).
Example:

#import "@preview/parize:0.2.0": par-indent
#set page(width: 15cm, margin: 1cm, height: auto)
#set par(first-line-indent: (amount: 2em, all: true), spacing: 1.5em)
#set block(stroke: red)// debug
#let test = [
#table(
columns: 4,
[
#lorem(2)
$
a^2 + b^2 = c^2
$
#lorem(2)
],
[
#lorem(2)
$
a^2 + b^2 = c^2
$
#lorem(2)],
[
#block[#lorem(2)]
$
a^2 + b^2 = c^2
$
#block[#lorem(2)] // not processed
#lorem(2)
+ #lorem(2)
- #lorem(2)
#lorem(2)
],
[
#block[#lorem(2)]
$
a^2 + b^2 = c^2
$
#block[#lorem(2)]
#lorem(2)
+ #lorem(2)
- #lorem(2)
#lorem(2)
],
)
]
#[
`apply-elem: ("all")`
#show: par-indent.with(use-par-leading: (apply-elem: "all"))
#test
]
#[
`block-text-leading: (list, enum, terms, math.equation)`
#show: par-indent.with(use-par-leading: (block-text-leading: (list, enum, terms, math.equation)))
#test
]
#[
`block-block-leading: (list, enum, terms, math.equation)`
#show: par-indent.with(use-par-leading: (block-block-leading: (list, enum, terms, math.equation)))
#test
]
#[
`text-block-leading: (list, enum, terms, math.equation)`
#show: par-indent.with(use-par-leading: (text-block-leading: (list, enum, terms, math.equation)))
#test
]
Notes
-
Native Typst Behavior: For block-level elements, if
block.aboveisautoand the preceding line is text or another block-level element withblock.below: auto, Typst insertspar.spacing.parizeallows usingpar.leadinginstead when no empty line exists. Otherwise, spacing follows the minimum ofblock.aboveand the previous element’sblock.below(autotreated as0pt), andparizedoes not intervene.- In particular, for
heading,title,quote, since the default ofblock.aboveandblock.beloware notauto, soparizedoesn’t affect their spacing by default. To include them, use:#show quote.where(block: true): set block(spacing: auto) #show heading: set block(spacing: auto) #show title: set block(spacing: auto) // ... #show : par-indent.with( use-par-leading: ( apply-elem: (quote, heading, title, /*other elements*/) ) )
- In particular, for
-
Caution: Avoid
use-par-leading: (apply-elem: "all"), which may disrupt packages relying on Typst’s existing paragraph model. -
Basic Elements:
block,pad,grid,stackandlayoutare not supported directly; if you wantpar.leadingcontrol for them, wrap them inparize-block.Example:

#import "@preview/parize:0.2.0": par-indent, parize-block #set page(width: 12cm, margin: 1cm, height: auto) #set par(first-line-indent: (amount: 2em, all: true), spacing: 1.5em) #[ #show: par-indent.with(use-par-leading: (apply-elem: (block, /*other block-level elements*/))) #let pad-wrapper = pad(y: 1em, lorem(2)) #table( columns: (1fr,) * 2, [ #set block(stroke: red) // debug #lorem(2) #pad-wrapper #pad-wrapper #lorem(2) #lorem(2) #parize-block(width: 100%, [#pad-wrapper]) #lorem(2) ], [ #set block(stroke: red) // debug #lorem(2) #pad-wrapper #pad-wrapper #lorem(2) #lorem(2) #parize-block(width: 100%, [#pad-wrapper]) #parize-block(width: 100%, [#pad-wrapper]) #lorem(2) ], ) ]parize-blockaccepts the same arguments asblock.
-
List Elements: When using
block-block-leadingfor lists (e.g.,use-par-leading: (block-block-leading: (list, enum, terms, ))), we ignore Typst’s PR#6242 to maintain consistent paragraph semantics (i.e., compatible with <0.14, not ≥0.14). This ensures thespacingparameter in lists controls only inter-item spacing, not spacing between the list and preceding text.- The native list model is limited, with only one
spacingparameter for both top and bottom margins. - Consider using the
itemize(≥0.3.0) package for enhanced list/enum functionality.
- The native list model is limited, with only one
Custom block-level container
If you want a custom block-level container to have paragraph spacing and paragraph indentation properties (i.e., to be treated as part of a paragraph), you can mark it with parize-par-above-flag and parize-par-below-flag before and after the container. When users apply parize to block, such containers will be processed as part of a paragraph. Alternatively, you can mark block-level elements with parize-prevention-label so that parize does not process them.
See inlinable-block.pdf (source) for more details.
Show-Rule Order
Suppose an element is overridden by show elem: .... If you want par-indent to apply to the overridden element, generally you should place show elem: ... after show: par-indent.
Example:
#import "@preview/parize:0.2.0": par-indent
#set page(width: 12cm, margin: 1cm, height: auto)
#[
#show "Test figure": set text(fill: red) // debug
#show "par-spacing": set text(fill: blue) // debug
#show "par-leading": set text(fill: green) // debug
#set par(first-line-indent: (amount: 2em, all: false), spacing: 1.5em)
#table(
columns: (1fr,) * 2,
[
#show: par-indent.with(
include-elem: (block,),
use-par-leading: (
block-text-leading: (block,),
text-block-leading: (block, figure),
),
)
#show figure: it => it.body
// `par-indent` receives `figure.body`, so it applies to `block`'s rules
#lorem(6) par-leading
#figure()[
#parize-par-above-flag
Test figure
#parize-par-below-flag
]<fig:test1>
unindented + par-leading #lorem(2)
Ref test: @fig:test1.
#lorem(5) par-spacing
#figure()[
#parize-par-above-flag
Test figure
#parize-par-below-flag
]
unindented + par-spacing #lorem(2)
],
[
#show figure: it => it.body
#show: par-indent.with(
include-elem: (block,),
use-par-leading: (
block-text-leading: (block,),
text-block-leading: (block, figure),
),
)
// `par-indent` recognizes `figure` as a block-level element, so it applies to `figure`'s rules
#lorem(6) par-leading
#figure()[
#parize-par-above-flag
Test figure
#parize-par-below-flag
]<fig:test2>
unindented + par-spacing #lorem(2)
Ref test: @fig:test2.
#lorem(5) par-spacing
#figure()[
#parize-par-above-flag
Test figure
#parize-par-below-flag
]
unindented + par-spacing #lorem(5)
],
)
]

Limitations
-
Version Compatibility: Supports Typst 0.13.0–0.14.2 only; may be incompatible with future versions (particularly PR#7931).
-
Convergence Behavior: Usually,
par-indentneeds at least 3 iterations to converge fully, but at most 4 iterations.