Front-end

Views & Templates

Views are plain PHP files under views/. There is no template-compilation step — rendering uses output buffering and include inside a scoped closure.

The $template facade

Every view gets a $template variable — a View facade exposing:

MethodPurpose
extends($layout)Declare the parent layout (e.g. "layouts/app").
section($name) / endSection()Open/close a named section (for layouts).
slot($name)Print a stored section (used inside layouts).
includes($path, $args)Render a partial from views/.
primitive($path, $args)Render a partial from views/primitives/.
component($name, $props) / endComponent()Render a component with slot capture.

Layouts & sections

views/layouts/app.php:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lunaris Layout</title>
</head>
<body>
    <main>
        <?php $template->slot("content") ?>
    </main>
</body>
</html>

views/welcome.php:

<?php $template->extends("layouts/app") ?>

<?php $template->section("content") ?>

<h1>Welcome to Lunaris</h1>

<p>Current Route: <?= route() ?></p>
<p>Current Url: <?= url() ?></p>

<?php $template->endSection(); ?>
Data first

Pass data from an action with response()->view("post", ["title" => "My Post"])->render() — the array is extracted into the view scope.

Partial includes

views/partials/nav.php is included from another view with:

<?php $template->includes("partials/nav") ?>

<?php $template->includes("partials/user", ["user" => $user]) ?>

Primitives

Small inline partials live in views/primitives/. views/primitives/button.php:

<button class="btn"><?= $label ?></button>
<?php $template->primitive("button", ["label" => "Save"]) ?>

// dot paths resolve to directories:
// "forms.button" → views/primitives/forms/button.php

Components with slots

views/components/card.php:

<div class="card">
    <h3><?= $title ?></h3>
    <div class="card-body"><?= $slot ?></div>
</div>

Everything between component() and endComponent() becomes the $slot:

<?php $template->component("card", ["title" => "Hello"]) ?>
    <p>This content becomes the slot.</p>
<?php $template->endComponent() ?>

Props and the $slot variable are available inside the component template; the $template facade is injected there too.

Path resolution

CallResolved path
response()->view("welcome")views/welcome.php
$template->includes("partials/nav")views/partials/nav.php
$template->primitive("forms.button")views/primitives/forms/button.php
$template->component("card")views/components/card.php

Dots are translated to directory separators for primitives and components. A missing file throws an AppException.

View statuses

Render an error view with the right HTTP status by calling status() on the renderer:

return response()
            ->view("errors/404")
            ->status(\System\Exceptions\Error::NOT_FOUND)
            ->render();

Only the three Error enum values (NOT_FOUND, FORBIDDEN, INTERNAL_SERVER_ERROR) are supported for views.

Part of the Lunaris documentation · Source on Codeberg · MIT License