HTMLki – seamless templating with the HTML spirit

HTMLki is a standalone templating engine for PHP 5.6 and up. Unlike most today’s templating systems that treat HTML as text and insert their own directives here and there HTMLki extends HTML by adding variables, loops, language lines, etc. directly into existing HTML constructs. Any valid HTML or PHP code is also a valid HTMLki template.

For example, here’s a simple menu in HTMLki:

xml<ul $menu>
  <li "$classes">
    <img $icon src=$icon>
    <a "$url" target=$target>$caption</a>

This is roughly equivalent to the following PHP code:

<?php if ($menu) {?>
    <?php foreach ($menu as $item) {?>
      <?php extract($item)?>

      <li <?=$classes 'class="'.htmlspecialchars($classes).'"' ''?>>
        <?php if ($icon) {?>
          <img src="<?=htmlspecialchars($icon)?>">
        <?php }?>

        <a href="<?=htmlspecialchars($url)?>"
           <?=$target 'target="'.htmlspecialchars($target).'"' ''?>>
    <?php }?>
<?php }?>


Because HTMLki is just HTML on steroids, any text editor with HTML/PHP syntax scheme gets highlighting right most of the time.

HTMLki imbues HTML with:

The above doesn’t require any additional integration code. However, you can tailor HTMLki into a markup ideal for your particular application by adding handlers for specific tags, attributes, etc.

For example, HTMLki can automatically expand src, href and action attributes into full URLs, or have tags like xml<errors> that output the list of errors linked to some input field (textarea, selectbox, etc.).


Grab the latest version here.

HTMLki is hosted on GitHub. Report any issues and make pull requests there.

HTMLki is released in public domain. I would appreciate attribution and a backlink, though.


HTMLki extends HTML syntax similar to the way LESS extend CSS. Any HTML file is a valid HTMLki file but not vice-versa.

This section describes the entire HTMLki language. It’s huge in the details but they stem from a few simple concepts.

{ expr }

Expression that is evaluated as PHP code and its result output with the following HTML characters quoting (htmlspecialchars with ENT_COMPAT):

If brackets’ content follows this pattern: [a-z_][a-zA-Z0-9_] it is converted into a variable: PHPechoMe }PHP$echoMe }. In rare cases when it’s a problem add some other symbols: PHP''.__NAMESPACE__ }.

See also dollar shortcut.

Examples (spaces inside brackets are optional and are often added to improve readability):

xml<h2>{ $title }</h2>
<time datetime="{ gmdate('Y-m-d') }">">

Equivalent to:

<h2><?php echo htmlspecialchars(($title)?></h2>
<time datetime="<?=htmlspecialchars((gmdate('Y-m-d'))?>">">

Trailing semicolon is optional:

xml<h2>{ $title; }</h2>

Dollar shortcut

Outputs an escaped variable content; starts with a dollar symbol ($) followed by a Latin letter and includes all consecutive a-z A-Z 0-9 _ symbols. Standalone $’s not matching this pattern are output as is.

Equivalent of wrapping this into brackets: { $var_123 }.


xmlHello, <strong>$user</strong>!
This costs <em>$5</em> (USD).

Equivalent to:

Hello, <strong>{ $user }</strong>!
Hello, <strong><?=htmlspecialchars($user)?></strong>!
This costs <em>$5</em> (USD).
This costs <em><?='$5'?></em> (USD).

Attention: unlike regular PHP string syntax, $obj->prop and $arr[0] output not value of the object property or array member but value of $obj or $arr% followed by raw–>propor[0]%% strings. Use PHP insets for this:

xmlHello, <strong>{ $user->name }</strong>!
Hello, <strong>{ $user['name'] }</strong>!

{= raw }

The same as bracket expression but doesn’t escape HTML characters. Should be used with care as it creates a XSS possibility.


xml<h2>{= $title }</h2>
<time datetime="{= gmdate('Y-m-d') }">">

Equivalent to:

<h2><?php echo $title?></h2>
<time datetime="<?=gmdate('Y-m-d')?>">">

Ruby-like function call

Ruby allows omitting brackets around the outermost function call for readability and brevity. A similar feature is supported within { } construct:

xmlYour balance is <b>${ number_format $user->balance }</b>.

Equivalent to:

Your balance is <b>$<?=number_format($user->balance)?></b>.


{[=][ ]Func PrefixArgs[;]}
Func    = one or more of \w \ : - >
Prefix  = one of " ' $ [ \w
Args    = anything

The above construct is replaced by PHP<?=Func(PrefixArgs)?> (with escaping added for non-{= form).

More examples:

xmlYour balance is <b>{= Locale::currencyAsHTML User::current()->balance }</b>.
Timezones: { join ', ', \DateTimeZone::listIdentifiers(\DateTimeZone::ANTARCTICA) }.

{{ escape

Double curly brackets are used to cancel their normal bracket expression processing.


xmlC-like languages use <kbd>{{</kbd> symbols a lot.

Equivalent to:

C-like languages use <kbd><?='{'?></kbd> symbols a lot.


Strings enclosed in quotes are first looked up in language strings table and then output. If the table contains no such string the original is used.

Strings may contain substitutions which are replaced with :1, :2, etc. For example:

Substitution characters are escaped by doubling them: This item costs {{$$5 USD}.«This item costs {$5 USD}.».

Note: HTML characters are escaped in substitutions. See also tag attribute expansion.



<p class="none">
  "Search query { htmlspecialchars($query) } returned no results."

Equivalent to:

<li><?php echo lang('Home')?></li>

<p class="none">
  <?=lang('Search query :1 returned no results.'htmlspecialchars($query))?>

Attention: because these are not PHP strings, "Hello, $user->name" becomes "Hello, :1->name". To reference an object field or array member use "Hello, { $user->name }".


Double quotes prevent the following text from being treated as a language string. They are output as is.


xml<em>""Hello, mister Robertson"".</em>

Equivalent to:

<em><?='"Hello, mister Robertson"'?>.</em>

Inside language strings quotes can be doubled to produce single quotes:

xml<em>"Hello, mister ""Robertson""."</em>

Equivalent to:

<em><?=$this->lang('Hello, mister "Robertson".')?></em>

Line merging

Often in HTML you have a long line which cannot be broken without unwanted side effects. The trailing-backslash syntax borrowed from C addresses this problem:

xml<a href="..." \
    onclick="alert('Okay, clicked.\n\
      Now what?')">
  Link text\

Equivalent to:

xml<a href="..." onclick="alert('Okay, clicked.\nNow what?')">
  Link text</a>

Example removing extra spaces inside HTML lists which would get in a way:

xml<span "list">\
  <a href="...">Item 1</a>\
  <a href="...">Item 2</a>\

Equivalent to:

xml<span class="list"><a href="...">Item 1</a><a href="...">Item 2</a></span>

Line merging happens before all other processing and can be used with constructs that are normally single-line, like PHP code, language strings and brackets:

<?='A very\
  long  \
"Hello, my dear \
{ a(brace, \
    form) }

Equivalent to:

<?='A verylong  string'?>
"Hello, my dear $user!"
{ a(brace, form) }

Formally, everything starting from \ until next non-whitespace symbol (on the following line) is removed. \ may be followed by any number of tabs and spaces before the line break. Whitespace before \ is preserved.


Each pair of trailing backslashes is shrunk to a single \ and no line merging happens (unless there is an unpaired \):

C-style languages \\
might also \\\
  cause the \\\\
  leaning toothpick syndrome (LTS).

Equivalent to:

C-style languages \
might also \cause the \\
  leaning toothpick syndrome (LTS).

Variable assignment

Apart from outputting variables using expressions and dollar shortcut you can set their values – either single-line or multi-line.

This construct must appear on a line by itself or it’s output as is. The leading dollar symbol ($) can only be preceded with whitespace.


xml$=varName php($expression)
$*varName 'assign, escape'.'& output'

The second form using asterisk (*) outputs new PHP$varName value with escaped HTML.


xml$=maxLength 50
<input "text" maxlength=$maxLength>

$*warning '<p class="warning">Warning!</p>'

Equivalent to:

<?php $maxLength 50?>
<input class="text" maxlength=<?=$maxLength?> />

<?php echo htmlspecialchars($warning '<p class="warning">Warning!</p>')?>
<?php echo htmlspecialchars($warning)?>

Trailing semicolon is optional:

xml$=maxLength 50;


Any HTMLki {='code'}

The closing circumflex symbol (^) can be replaced with an asterisk (*) to output the new content after assigning it to the variable. Note: HTML is not escaped, use dollar shortcut if you need to do this.

This is useful for creating sections that can be reused in nested or parent templates. Since these are regular PHP variables you can either pass them to functions or output their previous value before overriding in a child template.

Proper nesting and matching of opening/closing variable names is not checked or enforced. The variable is assigned only when its closing pair (=*var or =^var) is met.


  <li><a href=$home>Main page</a></li>
  <li><a href="about">About us</a></li>

<!-- Another template file: -->
  <li><a href="profile">Your profile</a></li>
  {= $menu }

Equivalent to:

  <?php ob_start()?>
  <li><a href="<?=$home?>">Main page</a></li>
  <li><a href="about">About us</a></li>
  <?php $menu ob_get_flush()?>

<!-- Another template file: -->
<?php ob_start()?>
  <li><a href="profile">Your profile</a></li>
  <?php echo $menu?>
<?php $menu 

Nesting example:

<header id=head>
  <nav $nav>
    <a "$item">$item</a>

As a result, PHP$nav contains:

  <a href="index.html">index.html</a>
  <a href="about.html">about.html</a>

And PHP$header contains:

xml<header id="head">
    <a href="index.html">index.html</a>
    <a href="about.html">about.html</a>


When variable name is followed by an at symbol (@) it sets either a flag attribute or a shorthand for given tag. Tag can be empty to set global flag attribute or shorthand – for example, $=attr@ disabled sets disabled flag attribute for all tags, such as:

Flag attribute form:

xml$=chk@input checked

<input checkbox chk>
<!-- "checkbox" is an alias of "input" so its attributes work for aliases: -->
<checkbox chk>

<!-- Result: -->
<input type="checkbox" checked="checked">

If attribute name matches with the part before @ the former can be omitted. Both lines have the same effect:

$=checked@input checked

Without defining chk as a flag attribute its value won’t be treated as a boolean flag. Compare:

xml<input chk>
<input chk=1>
<input chk="woosh">

<!-- Result: -->
<input chk="chk">
<input chk="1">
<input chk="woosh">

<input chk="chk">
<input chk="chk">
<input chk="chk">

$=chk@input checked
<input checked="checked">
<input checked="checked">
<input checked="checked">

Shorthand setting form:

xml$=print@meta media=print
<meta print>
<!-- Result: -->
<meta media="print">

$=ext@a target=external
<a ext href="">
<!-- Result: -->
<a target="external" href="">

$=blank@ someattr=
<foo blank>
<!-- Result: -->
<foo someattr="">


A default template is an amorphous blob with no input semantics – it takes what is given and in whatever form. Input checking is used to make a template more standalone, more robust to changes outside of it and have a clearly defined interface.


xml$>lastPage        int
$>currentPage@1   int <= $lastPage
Current page is $currentPage.
<a ${ $currentPage > 1 }?>Go back</enda>
<a ${ $currentPage < $lastPage }?>Go next</enda>

The first two lines are roughly equivalent to:

if (!isset($lastPage) or !is_int($lastPage)) { throw ...; }
if (!isset(
$currentPage)) { $currentPage 1; }
if (!
is_int($currentPage) or !($currentPage <= $lastPage)) { throw ...; }

By convention, all checks for template’s input variables are placed on top of the file. The syntax is similar to other variable assignments:

$>inputVar[@[default]] [type[!] | any] [of smth] [[if ]cond]

The shortest form is just $>someVar setting type to any and omitting all other parts.

List of common types, aliases and implied defaults:

anyspecial type disabling type checking
booleanalso bool; default is PHPfalse
integeralso int, num; default is PHP0
floatalso double, real; default is PHP0.0
stringalso str; default is PHP''
arrayalso hash, map; default is PHP[]
objectalso obj; default is PHPnew stdClass
callablenote that not only closures are callable but also things like PHP'trim', PHP['My''method'] and PHP$obj (if implements PHP__invoke())
resourcealso res
othernull, scalar, dir, file, etc.

A «type» is a name for which a top-level PHP function PHPis_XXX($value) is defined. Types without defaults use PHPnull as one.

List of coercion rules:

boolif null or scalar: if string representation is one of PHP'' 0 1 then PHP(bool) $value
integerif null or FILTER_VALIDATE_INT: PHP(int) $value
floatif null or integer or FILTER_VALIDATE_FLOAT: PHP(float) $value
stringif null or scalar: PHP(string) $value

Other types and other cases of the listed types are not coercible and fail the input check.

Attention: FILTER_XXX (of PHP filter_var()) trim their input so that PHP"\t123" is seen as a valid int and float.


Compartments are regular variables that are automatically passed between connected templates while accumulating values. Typical examples are «styles» and «scripts» on a webpage – one template may add the styles it depends on, another template – another set of styles until the final template outputs these compartments to the appropriate places on the page ( xml<head> and xml<body>).

A compartment variable is defined using a syntax similar to multi-line assignment, terminating with a $+var@key line. If key is set PHP$var gets at most one entry per that key. A special blank key clears old values. PHP$var is automatically created as or converted to an array.

Two main functions power this mechanism:

Note: if there’s no output between the two lines the variable is prepared but no item is created:



Roughly equivalent to this PHP 7.0+ code:

$head = (array) $head ?? [];
$head = [];   // but unlike $=head [], also declares $head as a compartment.

Let’s consider this «main» template (called by a frontend script/controller with page-specific variables):

  <link "this-page-styles.css">

<?php var_dump($head)?>

<table $rows>
  <include "inner">

<?php var_dump($head)?>

<rinclude "outer">

The first PHPvar_dump() indicates that PHP$head is an array with at least one member: PHP'<link href="..." ...>'. If this template has been Rinclude’d by some other, PHP$head may contain more items.

Example of the inner template:


  <script "super-table.js">

The second PHPvar_dump() indicates that PHP$head has at least two members now: the old PHP'<link ...>' and a new PHP'<script src="..." ...>'. However, even if inner is included many times there will be only one script entry because of the key used: @super-table.

Note: as a rule, @key must be unique across all connected templates – if values for this key are different it’s hard to predict which version will survive multiple inclusions.

The outer template might start with this snippet:

xml$>head@   array
<!DOCTYPE html>
    <each $head>
      {= $item }
    <!-- or even just: -->
    {= join $head }

The input checking line is optional and enforces PHP$head’s type as well as sets its default value (empty array).

The $+var@ form is useful to wrap a compartment’s contents before passing it to another template:

  <div id="content">
    <!-- $body is expected to exist and be an array -->
    {= join $body }

<rinclude "complete-page">

If $^var was used $body would become a string leading to type errors (array expected by the likely join($body) in complete-page).


Single-line form without opening $=var can be used to add/set an array’s member:

xml$+scripts 'jquery-2000.js'
$+styles@print 'media-print.css'
$+messages@ 'Old messages: '.join(', ', $messages)

Equivalent to:

$scripts  array_merge($scripts ?? [],  ['jquery-2000.js']);
$styles   array_merge($styles ?? [],   ['print' => 'media-print.css']);
$messages array_merge($messages ?? [],
'Old messages: '.join(', '$messages)]);

This form is unlike regular variable assignment which replaces the entire variable: PHP$=array 'value' equals PHP$array 'value'.


This construct is used to change the templater’s runtime configuration. It’s similar to creating tag attributes but works with any option (see PHPHTMLki\Config class).


$#defaultTag  'b'

$#evalPrefix  'namespace MyNS;'
<input value={ __NAMESPACE__ }>



<input type="text" value="MyNS">


It’s possible to escape the variable assignment expressions by doubling the leading dollar symbol ($).

Note that the assignment is only recognized when it occurs on a line by itself so you don’t need to escape it if it doesn’t (and escaping won’t be removed there).


xml  This is not an assignment:

Inline escaped dollar symbol: $$=var

Equivalent to:

This is not an assignment:

Inline escaped dollar symbol: <?='$=var'?>

Another example:

Content goes here...
...and continues here as well.
Now it ends:

Equivalent to:

<?php ob_start()?>
Content goes here...
and continues here as well.
Now it ends:
<?php $multilineVar ob_get_clean()?>

<?php code?>

It’s possible to use shorthand PHP tags regardless of short_open_tag configuration. The following tags are recognized:

PHP code snippets can be used along with normal HTML or HTMLki constructs; for example:

<ul "listing" $list>
  <li><a rel="nofollow">{ $title }</a></li>


Tags in HTMLki are just like normal HTML tags with certain additions:

  1. Default tag names – if you use spans or other tags a lot you can as well write xml<"b-e_m">forever</> to get xml<span class="b-e_m">forever</span>.
  2. Default attribute names – lets you omit src= for images, action= for forms, class= for other tags, etc.
  3. Flag attributes – it’s possible to specify readonly, checked, submit ( xmltype="submit") with a value alone and the attribute name will be guessed.
  4. Attribute expansion – lets you use PHP variables or expressions in attribute names and values and omit surrounding quotes around attribute values: xml<radio "radio {$i % 2 ? 'even' : 'odd'}" checked=checked id=$ctlID>.
  5. Multitags – lets you avoid xml<table><tbody><tr class="odd"> by using xml<table/tbody/tr "odd">. Works for closing tags as well.
  6. Shortcut tags – create xml<input type="checkbox" name="autologin"> from just xml<checkbox "autologin">.
  7. Enumeration attributes – list conditions to construct the attribute’s value, turning xml<a class=$current? class=$disabled?> into xml<a class="[current] [disabled]">.
  8. Single tag convertions – automatic turning of xml<link> into xml<link /> in XHTML and of xml<textarea rows=10 /> into xml<textarea rows="10"></textarea> in XHTML/HTML 5.
  9. Loops – turns a tag into a container for a set of items and adds «else-if» and «else» branches to such tags.
  10. Function calling – HTMLki tags ( xml<tag attr="value">) don’t have to be defined in the HTML spec, they can be user-defined functions such as xml<file "upload">, xml<each $list> or xml<include "partials/sidebar">.

By default all tags are single-line. If multi-line tags are enabled, the <a /))...'> below is treated as a single tag:

  if (s.match(/<a /)) {
    s += '>'

To avoid ambiguity in closing > detection, surround > with spaces to make it part of the attribute string:

  1. <if a->b> is a single tag (a special exception)
  2. <if a > b> is a single tag
  3. <if a >= b> is a single tag
  4. <if a>b> are a tag <if a> followed by plain text b>


HTMLki extends HTML format for specifying tag attributes ( xmlattr="value" second="yet another") with the following:

  1. Array variables for loops are specified after the tag name: xml<div $array $another>
  2. Default attributes are specified the same way: xml<img "src" "class">. They must go after array variables for tags not listed in loopTags config option: xml<img $glyphs "src" $thumbs "class"> is not the same as xml<img "src" "class" $glyphs $thumbs> (loop won’t be recognized) unless img is listed in loopTags – in this case both examples have the same effect.
  3. After the above go regular attributes.

Regular attributes are separated using spaces (just like in HTML) and generally have key=value form. However:

  1. The key part can be omitted creating an unnamed attribute which can be used by some tag functions (similar to passing parameters to functions by index in PHP: PHPfoo('a1'$a2)).
  2. You can create unnamed attribute beginning with either $ or " – to avoid confusion with loops and default attributes use =$value... form (thus = is explicit but key is empty).
    • once the first such attribute is present you don’t have to prefix all the following with = as loops/default attributes have to go before regular attributes and won’t be recognized at later positions anyway.
  3. Both key and value parts can’t contain spaces unless one of them or both are wrapped in quotes (again, just like in HTML but HTML only allows wrapping of values while HTMLki lets you wrap both): xml<img src=controls/button.gif>, xml<img src="templates/Calm Gray/button.gif">. Like language strings such attributes can contain variables or nested PHP expressions: xml<radio "$checked"> (contents of PHP$checked variable is used both as attribute name and value; if it’s empty nothing will be output). Note that without the quotes this will pass a list variable.
    • with usual tags keys are regular HTML attributes so spaces in them are not used; however, some user functions might use them.
    • watch out for unwanted substitutions: xml<input pattern="^\d{4}$"> – in order for {4} to be not treated as a code inset, double the opening bracket: ^\d{{4}$.
  4. Similar to above: key and/or value can be wrapped in curly brackets ({ }) instead of quotes to execute custom PHP code (must return a scalar, not an array). This works similarly to bracket construct ({ foo('bar') }). HTML symbols will be automatically quoted unless the opening bracket is immediately followed by an equality sign (=): xml<img src={ urlOf('My Template', 'btn.gif') }> (spaces inside brackets are optional). More examples:
    • xml<radio { $checked ? 'checked' : '' }>
    • xml<a {$anchorUsingName ? 'name' : 'id'}=$anthor> – this will output xml<a name="some_anchor"> if PHP$anchorUsingName is true and xml<a id="some_anchor"> if it’s false
    • xml<a attr={=$asIs}> – the value of PHP$asIs variable is inserted into attr attribute of xml<a> tag but it’s not escaped and thus if it contains " or other special symbol the markup will be broken.
    • xml<input { $off ? 'autocomplete=off' : '' }> – likely an error as the string is used both as key and value producing xml<input autocomplete="off=autocomplete=off">. Correct: xml<input autocomplete={ $off ? 'off' : null }>.
    • most custom functions only support simple key format: xml<each $list { ++$key }=...> would be given raw { ++$key } as a string attribute, not incremented $key’s value.
  5. If neither { } nor " are used to wrap key or value they are processed as if wrapped in " but ending on a first whitespace or > character.
    • xml<input value=$obj->prop> – same as PHP<input value="<?=$obj-?>">prop>. Correct: xml<input value={ $obj->prop }> or xml<input value="{ $obj->prop } and foo">.
    • xml<input value=$a['k']> – same as PHP<input value="<?=$a?>['k']">. Correct: xml<input value={ $a['k'] }> or xml<input value="{ $a['k'] } and foo"> (but not xmlvalue="$a[k]" or xmlvalue="$a['k']" as these are not PHP strings).
    • special case: value of form $identifier? as in xml<a class=$cur?> becomes identifier if it’s loosely true, otherwise null; mostly useful for enumeration attributes like class.
  6. If an unnamed attribute’s value is an array it’s expanded: xml<a "url" $extra> becomes xml<a href="url" class="c" target="_blank"> if PHP$extra is PHP['class' => 'c''target' => '_blank']. Warning: xml<a $extra> is treated as a loop tag; use: xml<a =$extra> (read above for =value syntax).
  7. If a key is an array multiple attributes are created with the same value: xml<input { ['autocomplete', 'autocorrect'] }=off>.

Note: HTML characters are escaped in substitutions. See also tag attribute expansion.

Default attributes

If a tag attribute is unnamed and wrapped in quotes it’s treated as a value for some commonly used attribute of that particular tag. For example, xml<img> doesn’t make sense without src. One tag can have multiple default attributes.

List of default attributes per tag:

ahref, class
buttonname, class
embedsrc, class
formaction, class
imgsrc, class
inputname, class
metaname, content
objectdata, class
optgrouplabel, class
paramname, value
selectname, class
textareaname, class

Others have a single default attribute – class; for example: xml<span "highlight">Notice</span> xml<span class="highlight">.

Flag attributes

In HTML attributes like readonly and selected can be either unspecified or have the same value as their name. With HTMLki you can specify them as flags where empty string or string «0» are false and everything else is true. They will be expanded to full HTML values.

List of flag attributes per tag:

audioautoplay, controls, loop
buttonautofocus, formnovalidate
inputautofocus, checked, readonly, formnovalidate, required
formaction, class
keygenautofocus, challenge, disabled
th, tdnowrap
videoautoplay, controls, loop, muted

Also, the following attributes can be used in any tag:

disabledHTML-compliant targets are xml<command>, xml<input>, xml<optgroup>, xml<option>, xml<select> and xml<textarea>.
hiddenhides any element in HTML 5

In some tags it’s possible to define attributes given just their value. For example, xml<form post enctype="text/plain"> is the same as xml<form method="post" enctype="text/plain"> (note that xml<form "post"> would be xml<form action="post"> because of default attributes).

List of shorthand values per tag:

anew expands into xmltarget="_blank" rel="noopener"
buttontype: button, reset, submit; xmltype="button", xmlvalue="1" are implied
commandtype: checkbox, command, radio
inputtype: button, checkbox, file, hidden, image, password, radio, reset, submit, text; selectonfocus is expanded into xmlonfocus=""; xmltype="text" is implied
keygenkeytype: rsa, dsa, ec
formmethod: get, post; aliases file, upload and multipart are expanded into xmlenctype="multipart/form-data"; xmlmethod="post" accept-charset="utf-8" are implied
litype: disc, square, circle
link xmlrel="stylesheet" is implied
paramvaluetype: data, ref, object
scriptxml:space: preserve; xmltype="text/javascript" is implied
style xmltype="text/css" is implied
textareaselectonfocus is expanded into xmlonfocus=""; xmlcols="50" rows="5" are implied

Also, the following attributes can be used in any tag:

alignleft, center, right, justify, top, middle, bottom
dirltr, rtl


Slash symbol (/) is used to separate multiple tag names inside a single tag construct. All attributes are related to the last tag in the chain. Preceding tags cannot be given attributes using this form; however, their implied attributes (e.g. xmlmethod="post" for xml<form>) are output.


xml<table/thead/tr class="head">
xml<form/p "info">
  <label>"Your name:" <input "name"></label>

Equivalent to:

    <tr class="head">
xml<form method="post">
  <p class="info">
    <label>Your name: <input type="text" name="name"></label>

Shortcut tags

Allows creating custom tags by aliasing other tags and possibly setting attributes when referred to them this way.


xml<get "settings">
  <checkbox "autologin">

Equivalent to:

xml<form method="get" action="settings">
  <input type="checkbox" name="autologin">

This creates complete aliases so default attributes, flags, etc. can be used with these shortcuts as well.

Attributes defined in that tag take priority over such aliased attributes so that xml<get "settings" method=post> becomes xml<form method="post" action="settings">.

List of shortcut tags:

password xml<input type="password">
hidden xml<input type="hidden">
file xml<input type="file">
check xml<input type="checkbox">
checkbox xml<input type="checkbox">
radio xml<input type="radio">
submit xml<button type="submit">
reset xml<button type="reset">
get xml<form method="get">
post xml<form method="post">

Default tags

By default if a <tag> construct is missing the tag name span is used. Other properties like default attributes work as usual.


xmlI use <"emphasis">so many</> spans that
it <"hinting">can</> become very tedious
to type. <"" id=endofstory />

Equivalent to:

xmlI use <span class="emphasis">so many</span> spans that
it <span class="hinting">can</span> become very tedious
to type. <span id="endofstory"></span>

See also configuration for changing default tag(s) used on the fly:

xml$#defaultTag  'b/i'
<"">Emphase me</>

Equivalent to:

xml<b><i>Emphase me</i></b>

Enumeration attributes

Several attributes in HTML, most notably class, are space- or comma-separated values. Constructing them with PHP is very tedious.

Normally, when a duplicate attribute appears its last value is used; with enumeration attributes all loosely true values are joined using the attribute’s separator.


xml<table $rows>
  <tr class=$isFirst? class={ in_array($id, $matches) ? 'match' : '' }>

The first class is defined using special ? value suffix which returns the variable’s name if it’s loosely true, otherwise null.

Skipping cycle initialization the second line is equivalent to:

  <tr class="<?=$isFirst 'isFirst' ''?>
             <?=in_array($id$matches) ? 'match' ''?>">

List of enumeration attributes per tag (space-separated unless noted otherwise):


Others have a single enumeration attribute – class.


xml<[/]tag[ attributes....]> construct is a function call that can be intercepted. By default if no implementation is defined an HTML tag is output with that name and with all attributes joined together, respecting loops if any list variable is given.

Function and tag names are converted to lower case: xml<SpAn> is identical to xml<span>.

Each function is given a PHPTagCall object with the list of given attributes, variables in the current template scope and other information.

There are 3 tag forms: opening ( xml<tag>), closing ( xml</tag>) and single ( xml<tag />); by default they affect the resulting HTML tag form and the closing tag.

If it’s a block function (like xml<each> or xml<if>) it can return an array to be iterated over; each member is an array of variables to be defined in each iteration. See also loops.

See also List of standard functions.

Loops and conditions

Any HTML tag can be converted into a loop by listing one or more variables or expressions after its tag name, possibly mixed with default attributes:

xml<ol $headers>
  <li>$key = $value</li>

To use an expression simply wrap it inside ${ ... }. Also, it’s possible to specify prefix that will be added to every loop variable so they don’t collide with nested loops. If none is specified variables are defined with their original names.

Let’s assume that PHP$headers is an array of arrays containing key and value keys:

xml<ol ${ array_filter($headers) }>
  <li>$key = $value</li>

<!-- With a prefix: -->
<ol $prefix{ array_filter($headers) }>
  <li>$key_prefix = $value_prefix</li>

When a non-traversable variable (neither an array nor an object implementing PHPTraversable interface) is looped over it gets converted to an array like this:

xml<div ${ isAuthenticated() }>
  "Hello, $userName!"

Boolean suffix

Often you want to iterate once and only if the value is loosely true. For this, append ? after the list variable:

xml<div ${ isAuthenticated() }?>

Equivalent to:

xml<div ${ !!isAuthenticated() }>

Or to:

<?php if (!!isAuthenticated()) {?>

Without ? if PHPisAuthenticated() returns PHP0 or PHP'' the xml<div> will be still output according to the rules. The question mark forces a boolean cast (using PHP rules) before array convertion, resulting in either 0 or 1 iterations and no loop variables.

It works the same with variables: xml<div $isAuth?>. Undefined variables trigger errors as usual.

There must be no spaces before ? or it will be treated as a separate attribute:

xml<div ${ isAuthenticated() } ?>
<div $isAuth ?>


The following variables are defined after the loop has started iterating:

Prefix above stands for a string given to the tag along with the loop expression/variable. If it’s empty then no separating underscore (_) is present.

No variables are created if prefix is _ or boolean suffix is used – such loops won’t interfere with other loops’ variables. In this example only tbody’s variables are created but td’s and ul’s are not:

xml<tbody $rows>
  <td $flag?>
    <ul $_{ getItems() }>


Each loop can contain zero or more else branches. Unless it’s an implicit loop tag each loop must be terminated with a symmetrical xml</endXXX> form where XXX must match the starting tag. The ending XXX tag is output if both the accepted else branch and end had tag names: xml<elseXXX>...</endXXX>.

xml<ul $menu>
  <li><a "$url">$title</a></li>
<elseul $secondaryMenu "secondary">
  <li><a "$url">$title</a></li>
  <p "none">"No menu items defined."</p>

For this reason all else branches with list(s) must have the same opening tag (elseul above) or tag nesting will be broken. For example:

xml<ul $menu>
<elsediv $secondary>
  <p "none">...</p>

In this example if there were no PHP$menu items xml<div>...</ul> is generated thus breaking element nesting. This won’t happen for xml<p "none"> because it has no lists (parameter starting with $).

Proper code would be:

xml<ul $menu>
<elseul $secondary>
  <p "none">...</p>


xml<ul $menu>
  <div $secondary>
    <p "none">...</p>

The difference between xml</endul> and xml</ul> is that the former ends looping and outputs the closing tag if at least one iteration had taken place while the latter always outputs the closing tag.

Both xml<else> and xml<end> can omit the tag name – this is usually used with custom tags like Each or If. In this case no ending tag is output even if a list has been iterated over.

HTMLki errors if opening tag doesn’t match xml<endXXX> tag (no such check is performed for non-list tags). The check is skipped for pairs ending on xml</end> so it’s recommended to use xml</endXXX> or xml</XXX> (for functions) where possible:

xml<each $a>
  <if $key>

<each $a>
  <if $key>

<!-- it won't be detected if the opening <each> and <if> change places -->
<each $a>
  <if $key>

Single-tag loops

Sometimes it’s convenient to loop over a single tag instead of a block. Let’s assume that we have an array of stylesheets:

'screen' => 'styles.css',
'print' => 'print.css',

We can output them all using this line:

xml<link $styles "$item" media=$key />

Trailing / is optional; it makes the single-tag nature of the loop more obvious to the (human) reader.

Block loops

Unlike single-tag loops block loops operate on an arbitrary chunk of code wrapped into xml<tag>...</endtag> pair. Regular variables are defined both inside these blocks and after them, although the latter should be considered an undesirable side-effect and might be changed in future.

Implicit loops

HTMLki can be configured to treat specific tags as always looping even if they don’t have any PHP variables passed. Of standard functions If and Each are such tags.

Both of the following examples have identical effect except the first works even when If isn’t configured as an implicit loop tag:

xml<if $var = is_scalar($var)>
  $$var is scalar.
xml<if is_scalar($var)>
  $$var is scalar.

Single tags

HTMLki can automatically treat certain tags as single tags requiring no ending tag and convert single tags specified in the template into their full forms with empty body if they’re not real short tags per HTML specification.


xml<link href=styles.css>
<script src=engine.js />

Equivalent to:

<link href="styles.css" />
script src="engine.js"></script>

You can still write regular HTML if you want which is equivalent to the code above.

Default list of single tags: area, base, basefont, br, col, frame, hr, img, input, link, meta, param – plus these functions: Lang, Include.

Standard functions


xml<include "partials/sidebar" expanded=1 user>

Include creates a new template, passes it certain variables according to the given regular attributes and merges its compartment variables on return.

Attribute syntax:

xml<include "..." [0]>
<include "..." [0 arrayvar] [[srcvar-]tplvar][=value] [...]>

Example of setting a non-scalar using a temporary variable (my/tpl gets PHP$theNameInside):

xml$=a   [1, 2, 3]
<include "my/tpl" a-theNameInside>

Looping form:

xml<include $list [$list_2 [...]] "tpl_name" [attributes...]>

Includes another template file as if its contents was directly placed at the point of xml<include>.



<rinclude "page">

Reverse include – the same as Include except that compartment variables are passed forward to the included script and are not merged on return.


xml<each $items "item">
  <b>Key:</b>    $key_item,
  <b>Index:</b>  $i_item,
  <b>Value:</b>  $item
  <p>"No $$items!"</p>

The same as Include but instead of filling a separate template file uses its own code block.


xml<if date('d') == 1>
  <p>First day today!</p>
<elseif date('d') == 32>
  <p>You gotta be kiddin'?!</p>
  <p>Today is { date('d.m.Y') }.</p>

Takes an arbitrary PHP expression and either executes its block or ignores it (passing on to else branches if there are any).


xml<lang "install_$status" { $fileCount + 1 }>

Outputs a language string with given name. Unlike language construct ("Anything $here.") it doesn’t replace PHP$variables and { code } with placeholders but uses that evaluated string as language string’s name.

For example, if PHP$status above is success then Lang will output language string install_success replacing placeholders (:1, :2, etc.) with given unnamed attributes. In this example :1 is replaced with { $fileCount + 1 } thus install_success string can look like this:

Installation has been successfully finished (:1 files copied).


xml<mailto "" "Subject line" />
E-mail the <mailto "">webmaster</mailto>

Outputs a «mailto» link with e-mail address obfuscated using HTML entities (&#...;). Takes up to two default attributes:

  1. E-mail address;
  2. Optional subject line

Sample generated link:

xml<a href="&#109;a&#x69;&#108;&#x74;&#111;:master&#x40;example&#46;com?subject=Subject%20line">master&#x40;example&#46;com</a>