Skip to Content
TemplatesCreating a Theme

Step-by-Step Theme Creation

A complete guide to creating a theme from scratch.

Create Structure

Minimal Structure

mkdir -p app/Themes/mytheme/assets/sass/theme
    • theme.json
          • _variables.scss

Full Structure

mkdir -p app/Themes/mytheme/{assets/{sass/{base,components,layouts,pages,theme},scripts,images},views/{layouts,components,pages,partials}}

Create theme.json

{ "name": "My Theme", "version": "1.0.0", "author": "Your Name", "description": "Custom theme based on standard", "extends": "standard" }

Override Colors

Create assets/sass/theme/_variables.scss:

// Light theme :root[data-theme=light] { // Accent color — indigo --accent: #6366f1; --accent-50: #eef2ff; --accent-100: #e0e7ff; --accent-200: #c7d2fe; --accent-300: #a5b4fc; --accent-400: #818cf8; --accent-500: #6366f1; --accent-600: #4f46e5; --accent-700: #4338ca; --accent-800: #3730a3; --accent-900: #312e81; // Backgrounds --background: #fafafa; --secondary: #f4f4f5; --third: #e4e4e7; // Text --text: #18181b; --text-muted: #71717a; } // Dark theme :root[data-theme=dark] { --accent: #818cf8; --accent-50: #1e1b4b; --accent-100: #312e81; --accent-500: #6366f1; --accent-600: #818cf8; --background: #09090b; --secondary: #18181b; --third: #27272a; --text: #fafafa; --text-muted: #a1a1aa; }

Create app.scss

Create assets/sass/app.scss:

// Include variables @import 'theme/variables'; // Include your components @import 'components/buttons'; @import 'components/cards'; @import 'layouts/header'; // Pages @import 'pages/home';

Customize Components

Buttons

assets/sass/components/_buttons.scss:

.btn { // Pill buttons border-radius: 50px; // Style font-weight: 600; letter-spacing: 0.025em; // Smooth transition @include transition(all); } // Gradient accent button .btn-accent { background: linear-gradient(135deg, var(--accent-500), var(--accent-600)); &:hover { background: linear-gradient(135deg, var(--accent-600), var(--accent-700)); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); } } // Outline with fill on hover .btn-outline-accent { background: transparent; border: 2px solid var(--accent); color: var(--accent); &:hover { background: var(--accent); color: white; } }

Cards

assets/sass/components/_cards.scss:

.card { background: var(--secondary); border: 1px solid var(--transp-1); border-radius: var(--border1); overflow: hidden; @include transition(all); &:hover { border-color: var(--accent-200); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08); } } .card-header { padding: var(--space-lg); border-bottom: 1px solid var(--transp-1); background: var(--third); } .card-body { padding: var(--space-lg); } .card-footer { padding: var(--space-md) var(--space-lg); border-top: 1px solid var(--transp-1); background: var(--third); }

Override Header

Create views/layouts/header.blade.php:

<header class="my-header"> <div class="container"> <div class="header__content"> {{-- Logo --}} <a href="{{ url('/') }}" class="header__logo"> <img src="{{ asset(config('app.logo')) }}" alt="{{ config('app.name') }}"> </a> {{-- Navigation --}} <nav class="header__nav"> @foreach (navbar()->all() as $item) @if (count($item['children']) === 0) <a href="{{ url($item['url']) }}" class="header__nav-link {{ active($item['url']) }}"> @if ($item['icon']) <x-icon :path="$item['icon']" /> @endif {{ __($item['title']) }} </a> @else <div class="header__nav-dropdown"> <button class="header__nav-link" data-dropdown-open="nav-{{ $loop->index }}" data-dropdown-hover="true"> @if ($item['icon']) <x-icon :path="$item['icon']" /> @endif {{ __($item['title']) }} <x-icon path="ph.regular.caret-down" /> </button> <div data-dropdown="nav-{{ $loop->index }}" class="dropdown"> @foreach ($item['children'] as $child) <a href="{{ url($child['url']) }}" class="dropdown__item"> @if ($child['icon']) <x-icon :path="$child['icon']" /> @endif {{ __($child['title']) }} </a> @endforeach </div> </div> @endif @endforeach </nav> {{-- Actions --}} <div class="header__actions"> <x-header.theme-switcher /> @auth <x-header.notifications /> <x-header.profile /> @else <x-button href="{{ url('login') }}" size="small" type="outline-accent"> @t('def.login') </x-button> <x-button href="{{ url('register') }}" size="small" type="accent"> @t('def.register') </x-button> @endauth </div> {{-- Mobile Menu --}} <button class="header__mobile-btn" data-trigger-right-sidebar> <x-icon path="ph.bold.list" /> </button> </div> </div> </header>

Header Styles

assets/sass/layouts/_header.scss:

.my-header { position: sticky; top: 0; z-index: 100; background: var(--blurred-background); backdrop-filter: blur(20px); border-bottom: 1px solid var(--transp-1); } .header__content { display: flex; align-items: center; justify-content: space-between; height: 72px; gap: var(--space-xl); } .header__logo { flex-shrink: 0; img { height: 36px; width: auto; } } .header__nav { display: flex; align-items: center; gap: var(--space-xs); @include media(tablet) { display: none; } } .header__nav-link { display: flex; align-items: center; gap: var(--space-xs); padding: var(--space-sm) var(--space-md); border-radius: var(--border05); font-size: var(--p); font-weight: 500; color: var(--text-muted); @include transition(all); &:hover, &.active { color: var(--text); background: var(--transp-1); } svg { width: 18px; height: 18px; } } .header__actions { display: flex; align-items: center; gap: var(--space-sm); } .header__mobile-btn { display: none; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: var(--border05); color: var(--text); @include transition(all); &:hover { background: var(--transp-1); } @include media(tablet) { display: flex; } }

Creating Your Own Component

Component

views/components/feature-card.blade.php:

@props([ 'title', 'description' => null, 'icon' => null, 'href' => null ]) @php $tag = $href ? 'a' : 'div'; @endphp <{{ $tag }} {{ $attributes->merge(['class' => 'feature-card']) }} @if($href) href="{{ $href }}" @endif> @if ($icon) <div class="feature-card__icon"> <x-icon :path="$icon" /> </div> @endif <div class="feature-card__content"> <h3 class="feature-card__title">{{ $title }}</h3> @if ($description) <p class="feature-card__description">{{ $description }}</p> @endif @if ($slot->isNotEmpty()) <div class="feature-card__body"> {{ $slot }} </div> @endif </div> </{{ $tag }}>

Component Styles

assets/sass/components/_feature-card.scss:

.feature-card { display: flex; flex-direction: column; padding: var(--space-xl); background: var(--secondary); border: 1px solid var(--transp-1); border-radius: var(--border1); text-decoration: none; color: inherit; @include transition(all); &:hover { border-color: var(--accent-200); transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); } &__icon { display: flex; align-items: center; justify-content: center; width: 56px; height: 56px; margin-bottom: var(--space-lg); background: var(--accent-100); color: var(--accent); border-radius: var(--border1); svg { width: 28px; height: 28px; } } &__title { margin-bottom: var(--space-sm); font-size: var(--h4); font-weight: 600; color: var(--text); } &__description { color: var(--text-muted); font-size: var(--p); line-height: var(--line-height); } }

Usage

<div class="grid grid-3 gap-lg"> <x-feature-card title="Fast Delivery" description="We will deliver your order within 24 hours" icon="ph.regular.truck" /> <x-feature-card title="Secure Payments" description="All transactions are encrypted" icon="ph.regular.shield-check" /> <x-feature-card title="24/7 Support" description="We are always ready to help you" icon="ph.regular.headset" href="/support" /> </div>

JavaScript (Optional)

assets/scripts/app.js:

document.addEventListener('DOMContentLoaded', () => { initAnimations(); }); function initAnimations() { // Appear on scroll const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-in'); } }); }, { threshold: 0.1 }); document.querySelectorAll('.animate-on-scroll').forEach(el => { observer.observe(el); }); } // Re-initialize after HTMX htmx.on('htmx:afterSwap', () => { initAnimations(); });

Theme Activation

  1. Go to Admin Panel → Settings → Themes
  2. Find your theme in the list
  3. Click Activate

Final Structure

    • theme.json
        • app.scss

Checklist

  • theme.json created with extends: "standard"
  • Colors overridden in _variables.scss
  • app.scss includes all styles
  • Theme works in light and dark mode
  • Responsive for mobile/tablet/desktop
  • Theme activated in admin panel

Clearing Cache

After changes:

php flute template:cache:clear php flute cache:clear

In development mode, SCSS is recompiled automatically when files change.