DALT.PHP
Framework Deep Dive6. Views & Frontend

Layouts & Partials

Reusable HTML components: head, nav, and footer

What Are Layouts?

Layouts are reusable pieces of HTML that appear on multiple pages. Instead of copying the same header and footer code to every view, you create them once and include them where needed.

In DALT.PHP, the learning platform (.dalt) provides three layout files:

  • head.php - HTML head, meta tags, Vite scripts
  • nav.php - Navigation bar with logo and links
  • footer.php - Footer with credits and closing tags

Where Layouts Live

.dalt/resources/views/layouts/
├── head.php
├── nav.php
└── footer.php

These are in .dalt because they're part of the learning platform UI, not your main app.

Using Layouts in a View

Here's a typical view structure:

<?php require base_path('.dalt/resources/views/layouts/head.php'); ?>
<?php require base_path('.dalt/resources/views/layouts/nav.php'); ?>

<!-- Your page content here -->
<main class="max-w-7xl mx-auto px-4 py-8">
    <h1>Welcome to the Course</h1>
    <p>Start learning PHP!</p>
</main>

<?php require base_path('.dalt/resources/views/layouts/footer.php'); ?>

The require statement includes the file's content at that exact spot.

The head.php Layout

Let's look at what's inside:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="view-transition" content="same-origin">
  <title>DALT.PHP Interactive Backend Debugging Playground</title>
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
  
  <style>
    /* Prevent FOUC (Flash of Unstyled Content) */
    body, html {
      background-color: #0f1117;
      color: #d1d5db;
    }
    [v-cloak] { display: none !important; }
    
    /* View Transitions */
    @view-transition { navigation: auto; }
    
    ::view-transition-old(root) {
      animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out;
    }
    ::view-transition-new(root) {
      animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in;
    }
    @keyframes fade-out { to { opacity: 0; } }
    @keyframes fade-in { from { opacity: 0; } }
  </style>
  
  <?= vite('.dalt/resources/js/app.js') ?>
</head>
<body class="min-h-screen antialiased bg-[#0f1117] text-gray-300">
<div class="min-h-screen flex flex-col">

Key Parts Explained

Meta Tags:

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
  • charset="UTF-8" - Supports all characters (emojis, international text)
  • viewport - Makes the site mobile-friendly (responsive design)

View Transitions:

<meta name="view-transition" content="same-origin">

This enables smooth page transitions in modern browsers. When you navigate between pages, they fade instead of instantly changing.

FOUC Prevention:

body, html {
  background-color: #0f1117;
  color: #d1d5db;
}

FOUC = "Flash of Unstyled Content"

Without this, you'd see:

  1. White page appears
  2. CSS loads
  3. Dark theme suddenly applies (jarring!)

With this inline style:

  • The page starts dark immediately
  • No flash of white

Vite Integration:

<?= vite('.dalt/resources/js/app.js') ?>

This loads your JavaScript and CSS. We'll cover this in detail in the next page.

Opening Tags:

<body class="min-h-screen antialiased bg-[#0f1117] text-gray-300">
<div class="min-h-screen flex flex-col">

Notice the <div> is opened but not closed. Why?

Because footer.php closes it. The layout is split across files:

  • head.php opens: <html>, <body>, <div>
  • footer.php closes: </div>, </body>, </html>

The nav.php Layout

<header class="border-b border-[#1e293b] bg-[#0f1117]/80 backdrop-blur text-gray-200 sticky top-0 z-50">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 py-4 flex items-center justify-between">
    
    <!-- Logo -->
    <a href="/" class="flex items-center gap-2 group">
      <span class="inline-block w-2 h-6 bg-[#93DA97] rounded group-hover:shadow-[0_0_10px_#93DA97] transition-all"></span>
      <span class="text-xl font-bold tracking-tight text-white group-hover:text-gray-200 transition-colors">DALT.PHP</span>
    </a>
    
    <!-- Navigation Links -->
    <nav class="flex items-center gap-6 text-sm">
      <a href="/learn" class="font-medium hover:text-[#93DA97] transition-colors">Learn</a>
      
      <?php if (file_exists(base_path('storage/auth_example_installed'))): ?>
        <?php if (\Core\Session::has('user')): ?>
          <!-- Logged in: show logout -->
          <form action="/session" method="POST" class="inline">
            <input type="hidden" name="_method" value="DELETE">
            <?= csrf_field() ?>
            <button type="submit" class="text-gray-400 hover:text-red-400 transition-colors">Logout</button>
          </form>
        <?php else: ?>
          <!-- Not logged in: show login/register -->
          <a href="/login" class="text-gray-400 hover:text-white transition-colors">Login</a>
          <a href="/register" class="text-gray-400 hover:text-white transition-colors">Register</a>
        <?php endif; ?>
      <?php endif; ?>
    </nav>
  </div>
</header>

Key Parts Explained

Sticky Header:

class="sticky top-0 z-50"

This makes the navigation bar:

  • Stay at the top when you scroll (sticky top-0)
  • Appear above other content (z-50)

Logo with Hover Effect:

<span class="inline-block w-2 h-6 bg-[#93DA97] rounded group-hover:shadow-[0_0_10px_#93DA97]"></span>

The green bar next to "DALT.PHP" glows when you hover over the logo. This uses Tailwind's group-hover feature.

Conditional Auth Links:

<?php if (file_exists(base_path('storage/auth_example_installed'))): ?>

This checks if the auth example is installed. If not, no login/register links appear.

Then it checks if the user is logged in:

<?php if (\Core\Session::has('user')): ?>
  • Logged in → Show "Logout" button
  • Not logged in → Show "Login" and "Register" links

Logout Form:

<form action="/session" method="POST" class="inline">
    <input type="hidden" name="_method" value="DELETE">
    <?= csrf_field() ?>
    <button type="submit">Logout</button>
</form>

Why a form instead of a link?

  • Logout should be POST (or DELETE), not GET
  • This prevents CSRF attacks (covered in the middleware section)

The _method field is a common pattern for simulating DELETE requests (since HTML forms only support GET and POST).

The footer.php Layout

</div>
<footer class="border-t bg-white">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 py-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-sm">
    
    <p class="text-gray-600">
      © <?= date('Y') ?> DALT.PHP - Interactive Backend Debugging Playground
    </p>
    
    <div class="flex items-center gap-3 text-gray-600">
      <a href="https://vuejs.org" target="_blank" class="hover:text-[#3E5F44] transition-colors">Vue 3</a>
      <span class="opacity-50">·</span>
      <a href="https://tailwindcss.com" target="_blank" class="hover:text-[#3E5F44] transition-colors">Tailwind v4</a>
      <span class="opacity-50">·</span>
      <a href="https://vitejs.dev" target="_blank" class="hover:text-[#3E5F44] transition-colors">Vite</a>
    </div>
  </div>
</footer>
</body>
</html>

Key Parts Explained

Closing the Container:

</div>

This closes the <div> that was opened in head.php.

Dynamic Year:

© <?= date('Y') ?>

date('Y') returns the current year (like "2024"). This keeps the copyright year up to date automatically.

Technology Credits:

<a href="https://vuejs.org" target="_blank">Vue 3</a>

The footer shows which technologies power the frontend:

  • Vue 3 (JavaScript framework)
  • Tailwind v4 (CSS framework)
  • Vite (build tool)

target="_blank" opens links in a new tab.

ELI5: How Layouts Work

Imagine building with LEGO:

  1. head.php is the base plate (foundation)
  2. nav.php is the roof piece (top of every building)
  3. Your content is the middle (unique to each building)
  4. footer.php is the finishing piece (bottom of every building)

You don't rebuild the base and roof for every building - you reuse them!

A Complete Example

Here's how a lesson page uses all three layouts:

<!-- .dalt/resources/views/learn/lesson.view.php -->

<?php require base_path('.dalt/resources/views/layouts/head.php'); ?>
<?php require base_path('.dalt/resources/views/layouts/nav.php'); ?>

<main class="flex-1 max-w-7xl mx-auto px-4 py-8 w-full">
    <h1 class="text-3xl font-bold mb-4"><?= htmlspecialchars($lesson['title']) ?></h1>
    
    <div class="prose prose-invert max-w-none">
        <?= $lesson['content'] ?>
    </div>
    
    <div class="mt-8 flex gap-4">
        <?php if ($previousLesson): ?>
            <a href="/learn/lesson/<?= $previousLesson['slug'] ?>" class="btn"> Previous</a>
        <?php endif; ?>
        
        <?php if ($nextLesson): ?>
            <a href="/learn/lesson/<?= $nextLesson['slug'] ?>" class="btn">Next</a>
        <?php endif; ?>
    </div>
</main>

<?php require base_path('.dalt/resources/views/layouts/footer.php'); ?>

The result:

  1. HTML head with meta tags and Vite
  2. Navigation bar with logo and links
  3. Lesson content (unique to this page)
  4. Footer with credits
  5. Closing HTML tags

Why Split Layouts This Way?

Advantages:

  • Change the header once, all pages update
  • Consistent look across the site
  • Less code duplication
  • Easy to maintain

Disadvantages:

  • Opening and closing tags are in different files (can be confusing)
  • Hard to see the full HTML structure at a glance
  • If you forget to include footer.php, you get broken HTML

Alternative: Layout Functions

Some frameworks use a different approach:

function layout($content) {
    require 'layouts/head.php';
    require 'layouts/nav.php';
    echo $content;
    require 'layouts/footer.php';
}

Then in views:

<?php ob_start(); ?>
<h1>My Page</h1>
<?php layout(ob_get_clean()); ?>

This keeps opening/closing tags together, but it's more complex for beginners.

DALT.PHP keeps it simple with direct require statements.

Customizing Layouts

If you want to customize the navigation for your app:

  1. Copy .dalt/resources/views/layouts/nav.php to resources/views/layouts/nav.php
  2. Modify your copy
  3. Update your views to require from resources/views/ instead of .dalt/resources/views/

Example:

<?php require base_path('resources/views/layouts/nav.php'); ?>

Now your custom nav is used instead of the platform's nav.

What's Good Here

  • Simple require statements (easy to understand)
  • Layouts are optional (you can skip them if needed)
  • Conditional auth links (only show when auth is installed)
  • Sticky navigation (good UX)
  • FOUC prevention (no white flash)
  • Dynamic copyright year (stays current)
  • CSRF protection on logout (secure)

Design Note

Layouts in DALT are plain PHP includes. That’s intentional:

  • It keeps HTML fully visible (no template engine magic).
  • It’s easy to debug: you can open the layout files and see exactly what is output.

If you want more flexibility later (titles, slots, inheritance), you can add it, but DALT starts with the simplest possible approach.

Common Patterns

Page-Specific Styles

<?php ob_start(); ?>
<style>
    .custom-class { color: red; }
</style>
<?php $extraStyles = ob_get_clean(); ?>

<?php require base_path('.dalt/resources/views/layouts/head.php'); ?>

<?php if (isset($extraStyles)) echo $extraStyles; ?>

This lets you add page-specific CSS.

Conditional Navigation Items

<!-- In nav.php -->
<?php if (isset($showAdminLink) && $showAdminLink): ?>
    <a href="/admin">Admin</a>
<?php endif; ?>

Then in your view:

<?php $showAdminLink = true; ?>
<?php require base_path('.dalt/resources/views/layouts/nav.php'); ?>
<a href="/learn" class="<?= urlIs('/learn') ? 'text-[#93DA97]' : '' ?>">Learn</a>

The urlIs() helper (from functions.php) checks if the current URL matches.

Testing Layouts

To verify layouts work:

  1. Create a test view that includes all three layouts
  2. Visit the page in your browser
  3. Check:
    • Page title appears in browser tab
    • Navigation bar is visible and sticky
    • Footer appears at the bottom
    • No unclosed HTML tags (check browser dev tools)
    • Responsive design works (resize browser window)

On this page