Create Blade template pages with Tailwind CSS for the Laravel ecommerce project. Includes shop layouts, admin dashboard layouts, and reusable components. Use when creating new pages, views, layouts, or UI components for the ecommerce website. Also use when the user asks to build a page, design a view, or create a Blade template.
This project uses two main layouts:
layouts/app.blade.php)For customer-facing pages (home, products, cart, checkout).
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Ecommerce') - {{ config('app.name') }}</title>
@yield('meta')
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
{{-- Header --}}
@include('components.header')
{{-- Main Content --}}
<main class="flex-1">
@yield('content')
</main>
{{-- Footer --}}
@include('components.footer')
@stack('scripts')
</body>
</html>
layouts/admin.blade.phpFor admin panel pages (dashboard, manage products, orders).
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Admin') - {{ config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-100 min-h-screen" x-data="{ sidebarOpen: true }">
<div class="flex">
{{-- Sidebar --}}
@include('admin.partials.sidebar')
{{-- Main --}}
<div class="flex-1 flex flex-col min-h-screen" :class="sidebarOpen ? 'ml-64' : 'ml-16'">
@include('admin.partials.topbar')
<main class="flex-1 p-6">
@if(session('success'))
<div class="bg-green-100 border border-green-300 text-green-700 px-4 py-3 rounded mb-4">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-100 border border-red-300 text-red-700 px-4 py-3 rounded mb-4">
{{ session('error') }}
</div>
@endif
@yield('content')
</main>
</div>
</div>
@stack('scripts')
</body>
</html>
{{-- admin/partials/sidebar.blade.php --}}
<aside class="fixed left-0 top-0 h-full bg-gray-900 text-white transition-all duration-300 z-30"
:class="sidebarOpen ? 'w-64' : 'w-16'">
<div class="p-4 border-b border-gray-700">
<h1 class="text-xl font-bold" x-show="sidebarOpen">Admin Panel</h1>
</div>
<nav class="mt-4 space-y-1">
<x-admin-nav-link route="admin.dashboard" icon="home">Dashboard</x-admin-nav-link>
<x-admin-nav-link route="admin.products.index" icon="box">Sản phẩm</x-admin-nav-link>
<x-admin-nav-link route="admin.categories.index" icon="folder">Danh mục</x-admin-nav-link>
<x-admin-nav-link route="admin.orders.index" icon="shopping-cart">Đơn hàng</x-admin-nav-link>
<x-admin-nav-link route="admin.inventory.index" icon="warehouse">Kho hàng</x-admin-nav-link>
<x-admin-nav-link route="admin.coupons.index" icon="tag">Khuyến mãi</x-admin-nav-link>
<x-admin-nav-link route="admin.reports.revenue" icon="chart-bar">Báo cáo</x-admin-nav-link>
<x-admin-nav-link route="admin.users.index" icon="users">Người dùng</x-admin-nav-link>
</nav>
</aside>
Key sections: Hero banner, Featured products, Categories grid, Flash sale countdown, New arrivals.
@extends('layouts.app')
@section('title', 'Trang chủ')
@section('content')
{{-- Hero --}}
<section class="bg-gradient-to-r from-blue-600 to-purple-700 text-white py-20">
<div class="max-w-7xl mx-auto px-4 text-center">
<h1 class="text-4xl md:text-5xl font-bold mb-4">Chào mừng đến cửa hàng</h1>
<p class="text-xl mb-8">Sản phẩm chất lượng, giá tốt nhất</p>
<a href="/san-pham" class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
Mua sắm ngay
</a>
</div>
</section>
{{-- Featured Products --}}
<section class="max-w-7xl mx-auto px-4 py-12">
<h2 class="text-2xl font-bold text-gray-800 mb-6">Sản phẩm nổi bật</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
@foreach($featuredProducts as $product)
<x-product-card :product="$product" />
@endforeach
</div>
</section>
@endsection
Key sections: Image gallery, Product info + price, Variant selector, Add to cart, Description tabs, Reviews, Related products.
Key sections: Cart items table, Quantity controls, Coupon input, Order summary, Checkout button.
Key sections: Address selection/form, Shipping method, Payment method (VNPay/MoMo/COD), Order summary, Place order button.
components/product-card.blade.php)@props(['product'])
<div class="bg-white rounded-lg shadow-sm hover:shadow-md transition overflow-hidden group">
<a href="{{ route('product.show', $product->slug) }}" class="block">
<div class="aspect-square overflow-hidden">
<img src="{{ $product->primaryImage ? Storage::url($product->primaryImage->image_path) : '/images/placeholder.jpg' }}"
alt="{{ $product->name }}"
class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
</div>
<div class="p-4">
<h3 class="text-sm font-medium text-gray-800 line-clamp-2 mb-2">{{ $product->name }}</h3>
<x-price :amount="$product->base_price" />
</div>
</a>
</div>
components/price.blade.php)@props(['amount', 'originalAmount' => null])
<div class="flex items-center gap-2">
<span class="text-lg font-bold text-red-600">{{ number_format($amount, 0, ',', '.') }}₫</span>
@if($originalAmount && $originalAmount > $amount)
<span class="text-sm text-gray-400 line-through">{{ number_format($originalAmount, 0, ',', '.') }}₫</span>
<span class="text-xs bg-red-100 text-red-600 px-1.5 py-0.5 rounded">
-{{ round(100 - ($amount / $originalAmount * 100)) }}%
</span>
@endif
</div>
components/star-rating.blade.php)@props(['rating' => 0, 'count' => 0])
<div class="flex items-center gap-1">
@for($i = 1; $i <= 5; $i++)
<svg class="w-4 h-4 {{ $i <= round($rating) ? 'text-yellow-400' : 'text-gray-300' }}" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
</svg>
@endfor
@if($count > 0)
<span class="text-sm text-gray-500 ml-1">({{ $count }})</span>
@endif
</div>
Consistent design values across all pages:
blue-600 (buttons, links, accents)red-600 (price, delete, errors)green-600 (active badges, success messages)bg-white rounded-lg shadow-smpy-12 (shop), p-6 (admin)max-w-7xl mx-auto px-4 (shop), max-w-full (admin)grid-cols-2 md:grid-cols-4 (product grid)rounded-lg (cards, buttons), rounded-full (badges, avatars)Use Alpine.js for lightweight interactivity:
{{-- Quantity selector --}}
<div x-data="{ qty: 1 }">
<button @click="qty = Math.max(1, qty - 1)" class="px-3 py-1 border rounded">-</button>
<input type="number" x-model="qty" name="quantity" min="1" class="w-16 text-center border rounded">
<button @click="qty++" class="px-3 py-1 border rounded">+</button>
</div>
{{-- Add to cart with AJAX --}}
<button @click="
fetch('/cart/add', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content},
body: JSON.stringify({product_variant_id: variantId, quantity: qty})
}).then(r => r.json()).then(data => { cartCount = data.cart_count })
" class="bg-blue-600 text-white px-6 py-3 rounded-lg">
Thêm vào giỏ hàng
</button>