Frontend

Blade Standards

File Organization

resources/views/
├── components/
│   ├── layouts/
│   │   ├── app.blade.php
│   │   └── auth.blade.php
│   ├── ui/
│   │   ├── button.blade.php
│   │   ├── modal.blade.php
│   │   └── card.blade.php
│   └── forms/
│       ├── input.blade.php
│       └── select.blade.php
├── pages/
│   ├── dashboard.blade.php
│   └── tickets/
│       ├── index.blade.php
│       └── show.blade.php
├── partials/
│   ├── header.blade.php
│   └── footer.blade.php
└── emails/
    └── welcome.blade.php

Naming Conventions

  • Use kebab-case for file names: ticket-list.blade.php
  • Use kebab-case for component names: <x-ui.button-group>
  • Group related components in subdirectories
// Good
<x-ui.button>
<x-forms.input>
<x-layouts.app>

// Bad
<x-Button>
<x-formInput>
<x-layouts_app>

Components

Use Component Syntax

Prefer the x- component syntax over @include:

// Good
<x-ui.card>
    <x-slot:header>Card Title</x-slot:header>
    Card content here
</x-ui.card>

// Bad
@include('partials.card', ['title' => 'Card Title', 'content' => 'Card content here'])

Pass Data via Attributes

// Good - Clear attribute binding
<x-ui.button type="submit" :disabled="$isProcessing">
    Save
</x-ui.button>

// Good - Spread attributes
<x-forms.input :$name :$value :$errors />

// Bad - Passing data via @include
@include('components.button', ['type' => 'submit', 'disabled' => $isProcessing])

Component Class Definition

<?php

declare(strict_types=1);

namespace App\View\Components\Ui;

use Illuminate\Contracts\View\View;
use Illuminate\View\Component;

final class Button extends Component
{
    public function __construct(
        public string $type = 'button',
        public string $variant = 'primary',
        public bool $disabled = false,
    ) {}

    public function render(): View
    {
        return view('components.ui.button');
    }
}

Slots

Named Slots

// Component definition (card.blade.php)
<div class="card">
    @if(isset($header))
        <div class="card-header">
            {{ $header }}
        </div>
    @endif

    <div class="card-body">
        {{ $slot }}
    </div>

    @if(isset($footer))
        <div class="card-footer">
            {{ $footer }}
        </div>
    @endif
</div>

// Usage
<x-ui.card>
    <x-slot:header>
        <h2>Title</h2>
    </x-slot:header>

    <p>Main content goes here.</p>

    <x-slot:footer>
        <x-ui.button>Save</x-ui.button>
    </x-slot:footer>
</x-ui.card>

Data Handling

Pass Data from Controllers

All data should be passed from controllers or Livewire components:

// Good - Data passed from controller
public function show(Ticket $ticket): View
{
    return view('tickets.show', [
        'ticket' => $ticket->load(['user', 'category']),
        'statuses' => Status::cases(),
    ]);
}

// Bad - Query in Blade file
@foreach(App\Models\Ticket::all() as $ticket)
    {{ $ticket->title }}
@endforeach

No Eloquent in Views

// Bad
{{ $user->tickets()->count() }}
{{ App\Models\Category::all() }}
@foreach($ticket->comments()->latest()->get() as $comment)

// Good - Pass computed data from controller
{{ $ticketCount }}
{{ $categories }}
@foreach($comments as $comment)

PHP Logic

Minimize PHP in Templates

// Bad - Complex logic in Blade
@php
    $total = 0;
    foreach ($items as $item) {
        if ($item->is_active) {
            $total += $item->price * $item->quantity;
        }
    }
@endphp

// Good - Compute in controller or model
{{ $order->formatted_total }}

Acceptable PHP Usage

// Simple formatting is acceptable
{{ $date->format('M j, Y') }}
{{ number_format($price, 2) }}
{{ Str::limit($description, 100) }}

Conditionals

Use Blade Directives

// Good
@if($user->isAdmin())
    <x-admin-panel />
@endif

@unless($tickets->isEmpty())
    <x-ticket-list :tickets="$tickets" />
@endunless

@auth
    <x-user-menu :user="$user" />
@endauth

@guest
    <x-login-button />
@endguest

// Avoid deeply nested conditionals - extract to components

Prefer Early Returns in Components

// In component class
public function shouldRender(): bool
{
    return $this->items->isNotEmpty();
}

Loops

Use Blade Loop Variables

@foreach($items as $item)
    <div @class([
        'item',
        'first' => $loop->first,
        'last' => $loop->last,
    ])>
        {{ $loop->iteration }}. {{ $item->name }}
    </div>
@endforeach

@forelse($tickets as $ticket)
    <x-ticket-card :ticket="$ticket" />
@empty
    <x-empty-state message="No tickets found" />
@endforelse

Translations

Always Use Translation Helpers

// Good
<h1>{{ __('tickets.index.title') }}</h1>
<p>{{ __('tickets.show.created_by', ['name' => $user->name]) }}</p>

<x-ui.button>
    {{ __('common.save') }}
</x-ui.button>

// Bad - Hardcoded strings
<h1>Tickets</h1>
<p>Created by {{ $user->name }}</p>

Translation Parameter Naming

Use camelCase for translation parameters:

// Good
{{ __('orders.total', ['itemCount' => $count, 'totalPrice' => $total]) }}

// Bad
{{ __('orders.total', ['item_count' => $count, 'total_price' => $total]) }}

Assets

Vite Directive

// In layout file
<!DOCTYPE html>
<html>
<head>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('styles')
</head>
<body>
    {{ $slot }}

    @stack('scripts')
</body>
</html>

Asset Paths

// Good
<img src="{{ Vite::asset('resources/images/logo.png') }}" alt="Logo">

// For public assets
<img src="{{ asset('images/logo.png') }}" alt="Logo">

JavaScript

Use Stacks for Page-Specific Scripts

// In layout
<head>
    @stack('head_scripts')
</head>
<body>
    {{ $slot }}

    @stack('footer_scripts')
</body>

// In page
@push('footer_scripts')
    <script>
        // Page-specific initialization
    </script>
@endpush

Prefer External Files

// Good - External file
@vite('resources/js/pages/dashboard.js')

// Acceptable - Small inline Alpine.js
<div x-data="{ open: false }">
    <button @click="open = !open">Toggle</button>
    <div x-show="open">Content</div>
</div>

// Bad - Large inline scripts
<script>
    // 50+ lines of JavaScript
</script>

Alpine.js for Interactivity

// Good - Alpine for simple interactions
<div x-data="{ showFilters: false }">
    <button @click="showFilters = !showFilters">
        {{ __('common.filters') }}
    </button>

    <div x-show="showFilters" x-collapse>
        <x-filters :$filters />
    </div>
</div>

CSS

Use Tailwind Classes

// Good - Tailwind utilities
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
    Save
</button>

// Good - Extract repeated patterns to components
<x-ui.button variant="primary">Save</x-ui.button>

External Stylesheets for Custom CSS

/* resources/css/app.css */
@import './components/calendar.css';
@import './components/editor.css';

/* Small custom styles can go directly in app.css */
.prose-custom {
    @apply prose prose-lg prose-blue;
}

Formatting

Attribute Order

// Recommended order
<x-component
    {{-- Type/variant --}}
    type="submit"
    variant="primary"
    {{-- State --}}
    :disabled="$isLoading"
    wire:loading.attr="disabled"
    {{-- Styling --}}
    class="mt-4"
>

Multi-line Attributes

// Good - Readable multi-line
<x-forms.input
    name="email"
    type="email"
    :value="old('email')"
    :error="$errors->first('email')"
    required
/>

// Bad - Long single line
<x-forms.input name="email" type="email" :value="old('email')" :error="$errors->first('email')" required />

Related Documentation