Layouts & Components
Stop copy-pasting HTML. Extract shared structure into reusable templates.
You will learn
- Why copy-pasting layouts across pages is a maintenance nightmare
- Use App::render() to compose pages from reusable templates
- Build your own components — cards, callouts, anything
- How template variables flow from handler to view
The problem
You have three pages. Each one needs the same <head>, navigation bar, and footer.
Right now, you're copy-pasting that boilerplate into every file. When you change the nav, you
change it in three places. When you have 20 pages, you change it in 20 places.
That's not sustainable. You need a layout.
Step 1: Create a master layout
A layout is just a PHP template that wraps your page content. Create template/_master.php:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars($title ?? 'My App') ?></title>
<link rel="stylesheet" href="/css/site.css">
</head>
<body>
<nav>...your nav here...</nav>
<main>
<?php App::render('/pages/' . $page); ?>
</main>
<footer>...</footer>
</body>
</html>
Step 2: Use it from your page
Now your public/about.php becomes three lines:
<?php use ZealPHP\App;
App::render('/_master', ['title' => 'About', 'page' => 'about']);
App::render() loads template/_master.php, extracts the variables
($title and $page) into scope, and executes it. The master template
then renders the page content with a nested App::render('/pages/about').
Change the nav once in _master.php — every page picks it up. That's the
power of a layout.
Building reusable components
The same pattern works for any reusable HTML. Think of a component as a stencil with holes — you lay the stencil down and fill in the holes with different data each time.
<!-- template/components/_card.php -->
<?php $variant ??= 'default'; ?>
<article class="card card-<?= htmlspecialchars($variant) ?>">
<h3><?= htmlspecialchars($title) ?></h3>
<p><?= $body ?></p>
</article>
Use it anywhere:
App::render('/components/_card', [
'title' => 'Fast',
'body' => '117k requests per second',
'variant' => 'highlight',
]);
No class, no JSX, no bundler. A component is just a PHP file that echoes HTML with variables.
function Card({ title, body, variant = "default" }) {
return (
<article className={`card card-${variant}`}>
<h3>{title}</h3>
<p>{body}</p>
</article>
);
}
// Requires: React, JSX transpiler, bundler, hydrationHow variables flow
When you call App::render('/_master', ['title' => 'About', 'page' => 'about']):
- ZealPHP loads
template/_master.php - The array values are extracted as local variables:
$title = 'About',$page = 'about' - The template runs with those variables in scope
- Output is captured and sent to the browser
Default values use PHP's null coalescing: $variant ??= 'default' sets a fallback
when the caller doesn't pass variant.
This page you're reading is built with nested App::render() calls. The "You will learn" box, this "Try it" block, the nav, the sidebar — each is a separate component template being rendered with App::render().
See the render method in action: Open render demo →
Preview: two more render methods
You'll learn these in later lessons, but you can preview them now:
| Method | Returns | Use when | |
|---|---|---|---|
render() | void (echoes) | Direct output — this lesson | Open → |
renderToString() | string | htmx fragments — Lesson 8 | Open → |
renderStream() | Generator | SSR streaming — Lesson 9 | Open → |
The streaming demo is the most visual — watch 12 rows arrive one by one over ~2 seconds. Each yield flushes a chunk to the browser immediately.
What does App::render('/components/_card', ['title' => 'Hi']) do?
Key Takeaways
App::render('/_master', [...])wraps pages in a shared layout- Components are PHP files that echo HTML with variables — no framework overhead
- Variables are extracted from the render array into the template's scope
- For now you only need
render()— two more methods come in later lessons