Laravel Blade templates are a unique formatting challenge. A single .blade.php file can contain all three of:

  1. Standard HTML tags (<div>, <p>, <section>)
  2. PHP code blocks (<?php ... ?>, {{ $variable }})
  3. Blade directives (@if, @foreach, @section, etc.)

A formatter needs to track indent changes from all three sources simultaneously and combine them correctly.

The directive pair engine

Blade directives come in opening/closing pairs. The formatter maps them to indent changes using three regex patterns:

// These directives CLOSE a block (reduce indent before printing this line):
const decreaseRegex = /^@(endif|endforeach|endfor|endwhile|endsection|endauth|endguest|endpush|endcan|endunless|endverbatim|endproduction|endenv|endonce)\b/;

// These directives OPEN a block (increase indent after printing this line):
const increaseRegex = /^@(if|foreach|for|while|section|auth|guest|push|can|unless|verbatim|production|env|once|hasSection)\b/;

// These directives are mid-block (dedent BEFORE printing, maintain same level after):
const bothRegex = /^@(else|elseif|empty)\b/;

The "both" case: @else, @elseif, @empty

Mid-block directives are the interesting edge case. Consider:

@if($user->isAdmin())
  <div>Admin Panel</div>
@else
  <div>User Panel</div>
@endif

@else should be printed at the same indent level as @if (one level in from its container), even though it's inside the @if block. Then the content after @else should be indented one level again.

The formatter handles this with isLeadingClosingBladeDirective() — which returns true for both @end* directives AND @else/@elseif/@empty. When this function returns true, the print indent is reduced by 1 before the line is output. After printing, the indent level stays the same (the bothRegex returns 0 from getBladeDirectiveIndentChange).

Combining Blade and HTML changes

The handleBladeFormatting() function computes both the HTML tag change and the Blade directive change, then applies them together:

function handleBladeFormatting(line, formattedLines, spaces, currentIndent) {
  const htmlChange         = getHTMLIndentChange(line);      // from HTML tags
  const leadingHtmlClose   = getLeadingClosingTagsCount(line); // leading </tags>
  const bladeChange        = getBladeDirectiveIndentChange(line); // from @directives
  const isLeadingBladeClose = isLeadingClosingBladeDirective(line);

  let printCloseCount = leadingHtmlClose;
  if (isLeadingBladeClose) printCloseCount += 1;

  // Print at reduced indent (closing tags + closing directives dedent before print)
  const printIndent = Math.max(0, currentIndent - printCloseCount);
  formattedLines.push(' '.repeat(printIndent * spaces) + line);

  // Next line's indent combines both HTML and Blade changes
  currentIndent = Math.max(0, currentIndent + htmlChange + bladeChange);
  return currentIndent;
}

This means a line like @endif</div> (closing a Blade block and an HTML tag on the same line) correctly dedents twice before printing and then reduces the running indent by 2 for subsequent lines.

---

Mustafa Kamal Hossain

Mustafa Kamal Hossain

Founder & Principal Engineer at Manfi. Passionate about Laravel, SaaS architecture, and high-performance engineering.