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
[ ... ] (any array)Yes
{ ... } (any object)Yes
"" (empty string)No
falseNo
"false"Yes
nullNo
Missing fieldNo

An empty array or object is truthy, so show-if="data.items" renders even when there is nothing to show. Test a list for emptiness with count(data.items) == 0.

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>

Combining Conditions

Use &&, || and ! to test more than one thing in a single condition:

xml
<frame show-if="data.account.active && !data.account.suspended">
  <p>In good standing</p>
</frame>

&& binds tighter than ||, and both bind looser than a comparison, so data.role == 'admin' && data.active groups the way it reads. ! binds tightest of all — tighter than a comparison — so negate a comparison with !(data.count == 0), not !data.count == 0.

In XML an & starts an entity, so a conjunction is written &amp;&amp; inside a Press file. Papermill also accepts a bare &&. || and ! need no escaping.

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.total }}</frame>

|| falls back on any falsy value, so a 0 or an empty string takes the default too. Where zero is a value worth printing, test for it instead: {{ data.total == 0 ? '0.00' : data.total }}.

Or using asset defaults:

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