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
- Go to Admin Panel → Settings → Themes
- Find your theme in the list
- Click Activate
Final Structure
- theme.json
- app.scss
Checklist
-
theme.jsoncreated withextends: "standard" - Colors overridden in
_variables.scss -
app.scssincludes 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:clearIn development mode, SCSS is recompiled automatically when files change.