Skip to content

Conditional Rendering

Press lets you show or hide any element based on whether data is present. This is how you build templates that adapt to different inputs -- optional sections, fallback values, and dynamic layouts.

The show-if Attribute

Add show-if to any element. The element is rendered only when the condition is truthy (present and non-empty):

xml
<frame show-if="data.executive-summary">
  <h2>Executive Summary</h2>
  <p>{{ data.executive-summary }}</p>
</frame>

If data.executive-summary is missing, empty, or null, the entire frame is skipped -- it takes up no space in the layout.

What Counts as Truthy?

ValueRendered?
"some text"Yes
123Yes
trueYes
[ ... ] (non-empty array)Yes
{ ... } (non-empty object)Yes
"" (empty string)No
falseNo
"false"Yes
nullNo
Missing fieldNo

Once your data has been merged, a null reads the same way a missing field does: it is falsy in show-if, and it renders as nothing when interpolated with .

The two differ in the merge itself. A field you leave out falls back to the default in your template; a field you send as null replaces that default. If your template defaults title to "Quarterly Report" and you send "title": null, the title renders empty.

Where an element needs a value to iterate or plot, a null leaves it nothing to work with. Sending "rows": null to a <repeat data="data.rows"> makes Papermill reject the document with message Cannot iterate over 'data.rows'. Should it be an array?.

Using show-if on Different Elements

On Frames

xml
<frame show-if="data.disclaimer" padding="12pt" background-color="#fef3c7">
  <p font-style="italic">{{ data.disclaimer }}</p>
</frame>

On Images

xml
<img show-if="data.company-logo" src="{{ data.company-logo }}" max-height="60pt" />

On Table Rows

xml
<table>
  <tr>
    <td>Subtotal</td>
    <td>{{ data.subtotal }}</td>
  </tr>
  <tr show-if="data.discount">
    <td>Discount</td>
    <td>-{{ data.discount }}</td>
  </tr>
  <tr show-if="data.tax">
    <td>Tax ({{ data.tax-rate }}%)</td>
    <td>{{ data.tax }}</td>
  </tr>
  <tr>
    <td font-style="bold">Total</td>
    <td font-style="bold">{{ data.total }}</td>
  </tr>
</table>

On Text Inline

xml
<p>
  Contact: {{ data.contact.name }}
  <span show-if="data.contact.phone"> | {{ data.contact.phone }}</span>
  <span show-if="data.contact.email"> | {{ data.contact.email }}</span>
</p>

Ternary Expressions

For switching between two values based on a condition, use ternary expressions inside {{ }}:

xml
{{ condition ? valueIfTrue : valueIfFalse }}

Dynamic Sizing

xml
<frame width="{{ data.sidebar ? '65%' : '100%' }}">
  <flow name="body" />
</frame>
<frame show-if="data.sidebar" width="30%">
  <flow name="sidebar" />
</frame>

Conditional Text

xml
<frame font-color="{{ data.overdue ? '#dc2626' : '#16a34a' }}">
  Status: {{ data.overdue ? 'Overdue' : 'Current' }}
</frame>

Conditional Styling

xml
<frame background-color="{{ data.priority ? '#fef2f2' : '#f9fafb' }}"
       border-color="{{ data.priority ? '#fca5a5' : '#e5e7eb' }}"
       border-weight="1pt" padding="12pt">
  <h3>{{ data.title }}</h3>
</frame>

The <show-if> Directive

In addition to the attribute form, Press also supports a <show-if> wrapper element with a condition attribute:

xml
<show-if condition="data.subtitle">
  <frame font-size="14pt" font-color="#6b7280">
    {{ data.subtitle }}
  </frame>
  <hr />
</show-if>

This is useful when you want to conditionally render multiple sibling elements together without wrapping them in an extra frame.

Common Patterns

Conditional Page Types

Use data to select different page layouts:

xml
<repeat group="sections" item="section">
  <page name="{{ section.data.page-type ? section.data.page-type : 'default-page' }}" />
</repeat>

Default Values

xml
<frame>{{ data.currency ? data.currency : '£' }}{{ data.total }}</frame>

Or using asset defaults:

xml
<img src="{{ data.logo ? data.logo : assets.data.defaults.logo }}" />