Docs

Theming

MonkeysLegion’s UI stack is driven by MLView and a tiny compile-time pipeline:

Loader  →  Parser  →  Compiler  →  Renderer  →  ✨ HTML
          ^                     ^
          |                     |
          └── ComponentNode / SlotNode  ──┘

You edit plain .ml.php files under resources/views/.

Loader finds them, Parser builds an AST (using ComponentNode & SlotNode), Compiler turns that AST into cached PHP, and Renderer includes the cache with your data array. All this is wrapped in the MLView façade—so day-to-day you call just $view->render().

1 · Base directory layout

app/
├─ Controller/
├─ Entity/
├─ Repository/
│
resources/
└─ views/
   ├─ layouts/
   │   └─ app.ml.php
   ├─ components/
   │   └─ Card.php   (class)
   └─ home.ml.php

Hot reload: The dev-server watches resources/views/** and rebuilds caches in <50 ms.

2 · Creating your base layout

resources/views/layouts/app.ml.php

<!doctype html>
<html lang="en" class="h-full bg-gray-50">
  <head>
    <meta charset="utf-8"/>
    <title>{{ $title ?? 'MonkeysLegion' }}</title>
    @vite('resources/css/app.css')
  </head>

  <body class="min-h-full">
    <header class="bg-primary text-white p-4">
      <h1 class="text-2xl font-bold">{{ $header ?? 'My App' }}</h1>
    </header>

    <main class="container mx-auto py-8">
      {{ $slot('default') }}
    </main>

    <footer class="py-4 text-center text-xs text-gray-500">
      © <?= date('Y') ?> Example Inc
    </footer>
  </body>
</html>

{{ … }} is HTML-escaped; {!! … !!} prints raw.

3 · Re-usable components

app/View/Components/Card.php

namespace App\View\Components;

use MonkeysLegion\Template\Component;

final class Card extends Component
{
    public function __construct(
        public bool $bordered = false,
        public bool $shadow   = true,
    ){}

    public function render(): string
    {
        $classes = "bg-white rounded-xl p-6";
        $classes .= $this->bordered ? " border border-gray-200" : "";
        $classes .= $this->shadow   ? " shadow-md"              : "";

        return <<<ML
<div class="$classes">
  <h3 class="text-lg font-semibold mb-2">{{ \$this->slot('title') }}</h3>
  <div>{{ \$this->slot('default') }}</div>
</div>
ML;
    }
}

Use it in any view:

<x-card bordered>
  <x-slot:title>Welcome!</x-slot:title>
  Thanks for trying MonkeysLegion.
</x-card>

ComponentNode & SlotNode in the parser AST guarantee slots are wired correctly.

4 · Live example (home.ml.php)

resources/views/home.ml.php

<x-layout>
  <x-slot:title>Hello ✨</x-slot:title>

  <x-card>
    <x-slot:title>Quick start</x-slot:title>
    Edit <code>resources/views/home.ml.php</code> and watch me hot-reload!
  </x-card>
</x-layout>

The first request:

  1. Loader grabs the template.

  2. Parser detects <x-layout>ComponentNode, two SlotNodes.

  3. Compiler emits PHP to .ml.cache.php under storage/cache/views/.

  4. Renderer includes that cache with your data.

Subsequent requests skip 1-3 until the source file’s mtime changes.

5 · Styling with Tailwind (default)

tailwind.config.js

module.exports = {
  content: ["../resources/views/**/*.ml.php"],
  theme: {
    extend: {
      colors: { primary: "#4F46E5" }
    }
  },
  darkMode: 'class',
}

Run Vite in watch mode:

npm run dev

Tailwind purges unused classes on npm run build.

6 · Dark mode toggle

In layouts/app.ml.php:

<html class="{{ $prefersDark ? 'dark' : '' }}">

Persist preference in a cookie or localStorage and flip the class from JS.

7 · Config-driven theming

config/theme.mlc

colors.primary = "#FF5733"
fonts.body = "Inter, sans-serif"

Retrieve in the layout:

$theme = $config->get('colors');
<header style="background: <?= $theme['primary'] ?>">

Override per-environment with config/env/prod.mlc (e.g. white-label branding).

8 · Emails

MLView works for transactional emails too:

resources/views/emails/welcome.ml.php

<x-email::layout>
  <x-slot:heading>Welcome, {{ $user->name }}!</x-slot:heading>
  We’re thrilled you joined.
</x-email::layout>

Email\Layout component outputs inlined CSS.

9 · Testing your theme

Tool

What to test

Playwright

Visual screenshots across break-points

Lighthouse CI

CLS, LCP, bundle size budgets

axe-core

WCAG 2.1 accessibility violations

Integrate these in GitHub Actions for every PR.

Recap

  • Loader → Parser → Compiler → Renderer pipeline keeps render time fast.

  • The AST uses ComponentNode and SlotNode to wire slots safely.

  • Tailwind + Vite give instant hot-refresh and production minification.

  • Config tokens (theme.mlc) let you re-brand without touching templates.

Happy theming! 🎨🦄