name: moonshine-appearance-v3 description: Use when customizing MoonShine v3 layouts, menus, colors, icons, assets, pages, dark mode, branding, or overall admin panel design.
MoonShine v3 Appearance
Covers layout templates, menu configuration, color theming, icons, asset management, dark mode, branding, and custom pages.
Layout overview
MoonShine provides two built-in layout templates:
- AppLayout -- standard sidebar layout with full header, footer, sidebar, breadcrumbs
- CompactLayout -- minimalistic theme extending AppLayout, adds
theme-minimalisticclass and overrides colors for a lighter look
The selected layout is published at app/MoonShine/Layouts/MoonShineLayout.php and configured in moonshine.layout.
Hierarchy
BaseLayout (abstract)
-> AppLayout (standard)
-> CompactLayout (compact/minimalistic)
-> BlankLayout (bare bones -- Head + Body only)
-> LoginLayout (authentication page)
Choosing a layout
// config/moonshine.php
'layout' => \MoonShine\Laravel\Layouts\AppLayout::class,
// Or via MoonShineServiceProvider
$config->layout(\App\MoonShine\Layouts\CustomLayout::class);
Creating a custom layout
Generate via artisan
php artisan moonshine:layout MyLayout
This creates app/MoonShine/Layouts/MyLayout.php.
Extending an existing layout
namespace App\MoonShine\Layouts;
use MoonShine\Laravel\Layouts\CompactLayout;
use MoonShine\UI\Components\Layout\Layout;
final class MoonShineLayout extends CompactLayout
{
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'My Company';
}
public function build(): Layout
{
return parent::build();
}
}
Override component methods (quick customization)
Instead of rewriting build(), override individual component methods:
protected function getHeadComponent(): Head { /* ... */ }
protected function getLogoComponent(): Logo { /* ... */ }
protected function getSidebarComponent(): Sidebar { /* ... */ }
protected function getHeaderComponent(): Header { /* ... */ }
protected function getTopBarComponent(): Topbar { /* ... */ }
protected function getFooterComponent(): Footer { /* ... */ }
protected function getProfileComponent(bool $sidebar = false): Profile { /* ... */ }
protected function getContentComponents(): array { /* ... */ }
protected function getLogo(bool $small = false): string { /* ... */ }
protected function getHomeUrl(): string { /* ... */ }
Layout slots
Quickly inject components into Sidebar or TopBar without overriding build():
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
];
}
protected function topBarSlot(): array
{
return [
// custom components in top bar
];
}
Using TopBar instead of Sidebar
public function build(): Layout
{
return Layout::make([
Html::make([
$this->getHeadComponent(),
Body::make([
Wrapper::make([
$this->getTopBarComponent(),
// $this->getSidebarComponent(), // removed
Div::make([
Flash::make(),
$this->getHeaderComponent(),
Content::make([
Components::make($this->getPage()->getComponents()),
]),
$this->getFooterComponent(),
])->class('layout-page'),
]),
])->class('theme-minimalistic'),
])
->customAttributes(['lang' => $this->getHeadLang()])
->withAlpineJs()
->withThemes(),
]);
}
If using both Sidebar and TopBar, TopBar must come first in the Wrapper.
See references/layouts.md for the full layout API.
Menu setup quick start
The menu is declared in the layout's menu() method:
use MoonShine\MenuManager\MenuItem;
use MoonShine\MenuManager\MenuGroup;
use MoonShine\MenuManager\MenuDivider;
protected function menu(): array
{
return [
MenuGroup::make('Content', [
MenuItem::make('Articles', ArticleResource::class, 'document-text'),
MenuItem::make('Categories', CategoryResource::class, 'tag'),
])->icon('newspaper'),
MenuDivider::make(),
MenuItem::make('Dashboard', DashboardPage::class, 'home'),
MenuItem::make('External', 'https://example.com', blank: true),
];
}
Key menu features
- MenuItem -- links to a Resource, Page, URL, or Closure
- MenuGroup -- groups items with an optional icon
- MenuDivider -- visual separator with optional label
- badge() -- show counts:
->badge(fn() => Comment::count()) - canSee() -- conditional display:
->canSee(fn() => auth()->user()->isAdmin()) - translatable() -- i18n support:
->translatable('menu') - whenActive() -- custom active state logic
- icon() -- Heroicons or custom SVGs
Autoloaded menu
Replace manual menu declaration with automatic discovery:
protected function menu(): array
{
return $this->autoloadMenu();
}
Control autoload behavior with attributes on resources/pages:
use MoonShine\MenuManager\Attributes\SkipMenu;
use MoonShine\MenuManager\Attributes\Group;
use MoonShine\MenuManager\Attributes\Order;
use MoonShine\MenuManager\Attributes\CanSee;
#[SkipMenu]
class ProfilePage extends Page {}
#[Group('Content', 'newspaper', translatable: true)]
#[Order(1)]
class ArticleResource extends ModelResource {}
#[CanSee(method: 'canViewInMenu')]
class SecretResource extends ModelResource
{
public function canViewInMenu(): bool
{
return auth()->user()->isAdmin();
}
}
See references/menu-system.md for the full menu API.
Color customization quick start
Override colors in the layout's colors() method:
use MoonShine\Contracts\ColorManager\ColorManagerContract;
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('#1E96FC')
->secondary('#1D8A99')
->body('249, 250, 251')
->dark('30, 31, 67', 'DEFAULT')
->dark('55, 65, 81', 700)
->dark('31, 41, 55', 800)
->dark('17, 24, 39', 900)
->successBg('209, 255, 209')
->successText('15, 99, 15')
->errorBg('255, 224, 224')
->errorText('81, 20, 20');
// Dark theme overrides
$colorManager
->body('27, 37, 59', dark: true)
->dark('39, 45, 69', 700, dark: true)
->dark('27, 37, 59', 800, dark: true)
->dark('15, 23, 42', 900, dark: true);
}
Global color override (ServiceProvider)
public function boot(ColorManagerContract $colors): void
{
$colors->primary('#7843e9');
}
Layout
colors()loads after ServiceProvider and takes precedence.
Semantic helpers
$colorManager->background('27, 37, 59'); // body + dark.800 + dark body
$colorManager->content('39, 45, 69'); // dark.700 + dark.900
$colorManager->tableRow('40, 51, 78'); // dark.600
$colorManager->borders('53, 69, 103'); // dark.300
$colorManager->dropdowns('48, 61, 93'); // dark.400
$colorManager->buttons('83, 103, 132'); // dark.50 + dark.500 + dark.400
$colorManager->dividers('74, 90, 121'); // dark.100 + dark.200
See references/colors.md, references/icons.md, and references/assets-branding.md for the full color, icon, and asset API.
Creating custom pages
Generate a page
php artisan moonshine:page CustomPage
Page structure
namespace App\MoonShine\Pages;
use MoonShine\Laravel\Pages\Page;
use MoonShine\UI\Components\Layout\Box;
use MoonShine\UI\Components\Layout\Grid;
use MoonShine\UI\Components\Layout\Column;
class CustomPage extends Page
{
protected string $title = 'Dashboard';
protected string $subtitle = 'Overview';
protected function components(): iterable
{
return [
Grid::make([
Column::make([
Box::make([/* ... */])
])->columnSpan(6),
Column::make([
Box::make([/* ... */])
])->columnSpan(6),
])
];
}
}
Assign a layout to a specific page
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
}
// Or via attribute
use MoonShine\Core\Attributes\Layout;
#[Layout(MyLayout::class)]
class CustomPage extends Page {}
Modify layout dynamically from a page
use MoonShine\Contracts\UI\LayoutContract;
protected function modifyLayout(LayoutContract $layout): LayoutContract
{
return $layout->title('Custom Title')->description('Custom description');
}
Page lifecycle hooks
onLoad()-- runs when the page is the active route (add assets, authorize, etc.)booted()-- runs when MoonShine creates the page instance (early initialization)
Dark mode setup
Always-dark theme
final class MoonShineLayout extends AppLayout
{
protected function isAlwaysDark(): bool
{
return true;
}
}
Theme switcher
The ThemeSwitcher component is included by default in getSidebarComponent() and getTopBarComponent(). It toggles the .dark class on the root <html> element.
Forcing dark mode on components inside light-themed areas
Sidebar, TopBar, and MobileBar are styled with dark colors by default. If you add custom components to them, force dark mode with:
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
Assets quick start
Add custom CSS/JS to a layout:
use MoonShine\AssetManager\Css;
use MoonShine\AssetManager\Js;
use MoonShine\AssetManager\InlineCss;
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/css/custom.css'),
Js::make('/js/custom.js')->defer(),
InlineCss::make(':root { --radius: 0.15rem; }'),
];
}
Vite integration
use Illuminate\Support\Facades\Vite;
use MoonShine\AssetManager\Js;
protected function assets(): array
{
return [
Js::make(Vite::asset('resources/js/app.js')),
];
}
Branding
Logo
protected function getLogo(bool $small = false): string
{
return $small ? '/images/logo-small.png' : '/images/logo.png';
}
Or via config:
// config/moonshine.php
'logo' => '/images/logo.png',
'logo_small' => '/images/logo-small.png',
Favicon
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => '/favicon/apple-touch-icon.png',
'32' => '/favicon/favicon-32x32.png',
'16' => '/favicon/favicon-16x16.png',
'safari-pinned-tab' => '/favicon/safari-pinned-tab.svg',
'web-manifest' => '/favicon/site.webmanifest',
]);
}
Footer
protected function getFooterMenu(): array
{
return ['https://docs.example.com' => 'Docs'];
}
protected function getFooterCopyright(): string
{
return sprintf('© %d My Company', now()->year);
}
Blade templates
Layouts can also be written in pure Blade using <x-moonshine::layout.*> components. Key components: layout, layout.html, layout.head, layout.body, layout.wrapper, layout.sidebar, layout.header, layout.content, layout.menu, layout.logo, layout.theme-switcher, layout.burger, layout.assets, layout.favicon.
See references/layouts.md for the full Blade component reference.
Cross-references
- moonshine-setup-v3 -- Installation, configuration, routing, and initial bootstrap
- moonshine-components-v3 -- UI components used inside layouts (Box, Grid, Column, Card, etc.)
- moonshine-resources-v3 -- ModelResource classes that populate menu items and pages
- moonshine-fields-v3 -- Field types used within page components