Basic Usage
Prerequisites: Quick Start; On this page: the tag API — creating elements, setting attributes, immediate rendering.
The examples assume the application has already loaded Composer's vendor/autoload.php; import the specific Pure\HTML, Pure\SVG, or Pure\Core symbols used by the snippet.
Tag API vs. compiled rendering
This page documents the tag API used for snippets, prototypes, and debugging; trees built this way render immediately via render() / print(). Production pages should compile shapes instead — see Compiled Rendering.
Basic Syntax
1. Creating HTML Elements
PurePHP provides two ways to create HTML elements:
Function Approach (Recommended for predefined tags)
PurePHP uses function calls to create HTML elements:
<?php
use function Pure\HTML\div;
use function Pure\HTML\h1;
use function Pure\HTML\p;
// Create a simple div element
div('Hello World')->print();
// Create nested elements
div(
h1('Title'),
p('Paragraph content')
)->class('container')->print();Magic Static Methods (For custom tags)
<?php
use Pure\Core\HTML;
// Create custom HTML elements using magic methods
// (the method name is the tag name: this renders <customTag class="custom">)
HTML::customTag('Custom content')->class('custom')->print();
// Perfect for web components or non-standard tags
HTML::myComponent(
HTML::header('Component Header'),
HTML::content('Component Body')
)->data_component('my-component')->print();Custom tags take the same children arguments as the functions, so a dynamic tag name works too:
<?php
use Pure\Core\HTML;
$tag = 'my-element';
HTML::{$tag}('Content')->class('dynamic')->print();Use a fixed allowlist for dynamic tag names. The renderer writes the tag name as supplied; it does not make an untrusted name safe.
2. Setting Attributes
Use method chaining to set element attributes:
<?php
use function Pure\HTML\div;
div('Content')
->class('container')
->style('background: #fff;')
->data_key('primary')
->id('main')
->print();Choosing the Right Approach
When to Use Each Method
Use Functions (Recommended for most cases)
- Best for: Standard HTML tags, everyday development
- Advantages: Clean syntax, good performance, excellent readability
- Example:
div(),p(),span(), etc.
Use Magic Static Methods
- Best for: Custom tags, web components, dynamic tag names
- Advantages: Works with any tag name, elegant syntax
- Example:
HTML::customElement(),HTML::webComponent()
<?php
use function Pure\HTML\div;
use Pure\Core\HTML;
// Function approach - standard tags
$standard = div('Standard content')->class('container');
// Magic method - custom tags
$custom = HTML::myCustomTag('Custom content')->data_component('special');Important Usage Notes
1. String Content vs Raw Content
String children and ordinary attribute values are escaped as HTML syntax. This prevents a string from becoming a new tag or quote-breaking attribute, and keeps comparison text such as 2<3 or a<b visible. It is not a complete URL or script policy: href/src schemes, event-handler attributes, style, and dynamic tag names still need validation or a Content Security Policy.
<?php
use Pure\Core\Raw;
use function Pure\HTML\div;
// ✅ String content is escaped, not parsed
div('<p>This is shown as text</p>')->print();
// Output: <div><p>This is shown as text</p></div>
// ✅ Use Raw::of to emit trusted markup
div(Raw::of('<p>This is preserved</p>'))->print();
// Output: <div><p>This is preserved</p></div>Markup-looking strings are shown as text instead of being parsed. This boundary is syntactic: it does not validate a URL such as javascript: or an event attribute, and it cannot make Raw::of() safe.
Why this matters:
- Syntax safety: Escaping prevents ordinary text and attributes from creating markup
- No data loss: Text that merely looks like markup is kept visible
- Intentionality: Emitting markup requires an explicit
Raw::of()wrapper - Defense in depth: Validate URL schemes and active attributes, restrict dynamic tag names, and use CSP
When to use Raw::of():
- Including pre-formatted HTML/XML content you control
- Embedding a template or external document after validating its source
- Working with trusted HTML/XML strings
- Including JavaScript or CSS only when the document's CSP and trust model allow it
2. className Alias
Since class is a PHP keyword, PurePHP provides className as an alias:
<?php
use function Pure\HTML\div;
// Both ways work
div('Content')->class('container')->print();
div('Content')->className('container')->print();3. Built-in Utility Functions
The class() method has a built-in clx function, and style() has a built-in sty function to handle arrays and conditional parameters:
<?php
use function Pure\HTML\div;
$isActive = true;
$isLarge = false;
div('Content')
->class('btn', $isActive ? 'active' : null, $isLarge ? 'large' : null)
->style(['color' => 'red', 'font-size' => '16px'])
->print();When you need to merge class/style strings on their own, reach for clx() / sty() from Utility Functions.
4. Attribute Naming Rules
Since - has special meaning in PHP, attributes like data-id need to be written as data_id:
<?php
use function Pure\HTML\div;
div('Content')
->data_id('123') // corresponds to data-id="123"
->data_type('card') // corresponds to data-type="card"
->aria_label('Button') // corresponds to aria-label="Button"
->print();5. Adding Child Elements
You can add multiple child elements through parameters:
<?php
use function Pure\HTML\div;
use function Pure\HTML\p;
div(
p('First paragraph'),
p('Second paragraph'),
p('Third paragraph')
)->class('content')->print();Common HTML Tags
PurePHP supports all common HTML tags:
<?php
use function Pure\HTML\{
div, span, p, h1, h2, h3, h4, h5, h6,
a, img, ul, ol, li, table, tr, td, th,
form, input, button, textarea, select, option
};
// Create a link
a('Click here')->href('https://example.com')->print();
// Create an image
img()->src('image.jpg')->alt('Image description')->print();
// Create a list
ul(
li('Item 1'),
li('Item 2'),
li('Item 3')
)->class('list')->print();
// Create a form
form(
input()->type('text')->name('username'),
input()->type('password')->name('password'),
button('Submit')->type('submit')
)->method('POST')->action('/login')->print();SVG Support
PurePHP has built-in support for SVG tags:
<?php
use function Pure\SVG\{svg, circle, rect, path};
// Create a simple circle
svg(
circle()
->cx('50')
->cy('50')
->r('40')
->stroke('black')
->stroke_width('3')
->fill('red')
)->width('100')->height('100')->print();
// Create a rectangle
svg(
rect()
->x('10')
->y('10')
->width('80')
->height('80')
->fill('blue')
)->width('100')->height('100')->print();SVG is treated as a fragment and does not add an xmlns attribute automatically. For a standalone .svg file, set the namespace explicitly with ->xmlns('http://www.w3.org/2000/svg'); save() still supplies the XML declaration unless you pass a custom header.
Conditional Rendering
Use PHP conditional statements for conditional rendering:
<?php
use function Pure\HTML\{div, p};
$isLoggedIn = true;
div(
$isLoggedIn ? p('Welcome back!') : p('Please log in')
)->class('message')->print();With compiled rendering, the condition becomes a Slot::if() placeholder and the branches are shapes. Slots such as Slot::value() stand in for the values that are bound at render time:
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\{div, p};
function Message(bool $isLoggedIn): string
{
static $render;
$render ??= Compile::shape(
div(
Slot::if(
'isLoggedIn',
p('Welcome back!'),
p('Please log in')
)
)->class('message')
);
return $render(['isLoggedIn' => $isLoggedIn]);
}
echo Message(true);Slot::if() reads the current data scope, a missing key is false, and the shape is memoized in static so it is built once per process.
Loop Rendering
Use PHP loop statements to render lists:
<?php
use function Pure\HTML\{ul, li};
$items = ['Apple', 'Banana', 'Orange'];
ul(
...array_map(fn($item) => li($item), $items)
)->class('fruits')->print();With compiled rendering, lists are Slot::each() slots: the item shape is rendered for every element of the bound iterable, with Slot::value() marking the value to bind:
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\{ul, li};
function Fruits(array $items): string
{
static $render;
$render ??= Compile::shape(
ul(Slot::each('items', li(Slot::value('name'))))->class('fruits')
);
return $render(['items' => $items]);
}
echo Fruits(['Apple', 'Banana', 'Orange']);
echo Fruits([
['name' => 'Apple'],
['name' => 'Banana'],
['name' => 'Orange'],
]);An item is the scope its shape reads, so ['name' => 'Apple'] supplies that key. Because the shape renders one slot, an item may also be the value itself, which is why the first call above passes the strings as they are.
Style Handling
1. Inline Styles
<?php
use function Pure\HTML\div;
div('Content')
->style('
background: #f0f0f0;
padding: 20px;
border-radius: 8px;
')
->print();2. Class Name Handling
One class() call joins all of its arguments, and a second class() call replaces the value of the first, so pass every class in a single call:
<?php
use function Pure\HTML\div;
$isActive = true;
div('Content')
->class('container', $isActive ? 'active' : 'inactive')
->print();Next Steps
- Core Concepts - Tag trees, shapes, slots and components
- Props and Slots - Slot types and the data-binding reference
- Components - Components wrap shapes
- Compiled Rendering - How a component's template compiles
- SVG and XML Support - Learn about SVG graphics and XML documents