Guide 04 · Developer docs

Input types, layouts, components and images

The platform's central abstraction, in one line: an input type defines what an editor can enter, a layout area binds one input type to a named slot, a layout groups areas and points at a template, a record or component holds the content for each area, and the template reads it back. Read this book straight after the template languages.

L1–L3
Unit 07 · The shorthand, and working with each input type

Dates, numbers, colours and code areas

Formatting a date or datetime in both engines and handling an empty one; what number, email and math areas check, which is less than their names suggest; feeding a colour into a CSS custom property; and the escaping decision for a code area.

Who this is for. Developers rendering the input types beyond plain text. After this page you can format dates without fatal errors, convert numbers before using them, keep colours in the stylesheet's hands and decide how to output code.

Date and datetime

A date area stores a date such as 2026-10-19; a datetime adds a time. Through the shorthand you get a printable date: an object that prints in the site's date format (its datetime format, for a datetime) and passes any other method, such as format(), to the date inside it. It is null when the area is empty, so test it first:

Template code
{if $component->eventDate}
    <time datetime="{$component->eventDate->format('Y-m-d')}">{$component->eventDate}</time>
{/if}
{% if component.eventDate %}
    <time datetime="{{ component.eventDate.format('Y-m-d') }}">{{ component.eventDate }}</time>
{% endif %}

The datetime attribute takes a fixed format through format(); the element content prints the date as it is, in the site's format. Both are safe in live preview, because a date object is never wrapped.

The explicit getter behaves differently. getAreaDate() returns a plain PHP DateTime, or null, not a printable date. A DateTime cannot be printed as it is: the page fails with Object of class DateTime could not be converted to string. Format it or compare it, never print it bare:

Template code
{$start = $component->getAreaDate('startDate')}
{if $start && $start->getTimestamp() < $smarty.now}
    <p class="notice">Started {$start->format('j F Y')}</p>
{/if}
{% set start = component.getAreaDate('startDate') %}
{% if start and start.getTimestamp() < date().getTimestamp() %}
    <p class="notice">Started {{ start.format('j F Y') }}</p>
{% endif %}

Smarty also registers a |format modifier, which formats a DateTime in the site's date format. Templates rarely use it, and it refuses the shorthand's printable date; format() on the object works for both, so prefer it.

Timezone. Dates are presented in the site's timezone. The platform sets PHP's timezone to the site's on every request, and the explicit getters convert their dates to that same timezone, so on a site with one timezone nothing shifts: what the author typed is what you get.

Number

A number area's editor is a browser number field, and the value is stored as text. The browser's field steers authors towards whole numbers, but neither the admin form nor the API enforces a sign or rejects decimals. Convert the value before comparing or calculating with it, and read it with getAreaText(), because in live preview the shorthand gives the wrapped text:

Template code
{$columns = intval($component->getAreaText('columns'))}
<div class="grid" style="--columns: {max(1, $columns)}">
{% set columns = component.getAreaText('columns')|integer %}
<div class="grid" style="--columns: {{ max(1, columns) }}">

In Smarty intval() is written as a function, because the security policy allows PHP functions but not PHP modifiers; Twig on Core dna has an |integer filter. Where a value must stay within a range, enforce it in the template, as max() does above.

Email

An email area's editor is a browser email field, which checks the format in the admin. On the server, treat it as any other text: escape it, and add a regex to the area if the format must be checked there too.

Math

A math area stores a formula as text, in LaTeX such as \frac{a}{b}. The platform neither evaluates nor renders it. To display it, load a client-side renderer such as KaTeX or MathJax and print the value where the renderer looks for it.

Colour

A colour area stores the colour as text. Hand it to the stylesheet as a CSS custom property rather than styling the element directly, so the design decides where the colour is used and what happens without one. Leave the property out when the area is empty, so the stylesheet's fallback applies:

Template code
{$accent = $component->getAreaText('accent')}
<section class="banner"{if $accent} style="--banner-accent: {$accent}"{/if}>
{% set accent = component.getAreaText('accent') %}
<section class="banner"{% if accent %} style="--banner-accent: {{ accent }}"{% endif %}>
CSS
.banner {
    border-top: 4px solid var(--banner-accent, var(--brand));
}
A colour gets the wrapper too

A colour looks like safe plain text, but the shorthand wraps it for live preview like any text area, so style="--accent: {{ component.accent }}" breaks the style attribute in live preview, and only there. Inside style, always read it with getAreaText().

Code

A code area stores whatever the author types, unvalidated, and like any text area outputs it unescaped. What to do with it depends on why the area exists:

  • To show code as code, escape it.
  • To run markup or a script, such as a tracking snippet, output it raw. Whoever can edit that area can then put script on the site, so limit who can. On a site that sends a Content Security Policy, an inline <script> runs only when it carries the page's nonce, $_inlineScriptNonce; without it the browser refuses the script silently.
Template code
{* shown as code: escaped *}
<pre><code>{$component->getAreaText('sample')|escape}</code></pre>

{* run as a script: raw, inside a script tag that carries the nonce *}
<script nonce="{$_inlineScriptNonce}">{$component->getAreaText('script')}</script>
{# shown as code: escaped #}
<pre><code>{{ component.getAreaText('sample')|e }}</code></pre>

{# run as a script: raw, inside a script tag that carries the nonce #}
<script nonce="{{ _inlineScriptNonce }}">{{ component.getAreaText('script') }}</script>

Both use getAreaText(): one is escaped and the other sits inside a script, and the shorthand's live-preview wrapper belongs in neither. A code area is a text area, so images from the file manager in it are turned back into image markup on output, and a sample containing one does not print exactly as typed; record links are rewritten only in rich text, not here. The engine side of template security is on The template security policy and Twig in Core dna.

Go deeper

Was this page helpful?

Your answer goes to the docs team.