Layouts
A layout is the frame around a page: the <head>, the menu, the top bar, the footer, the notification area and the appearance preferences.
Quick example
@extends('layouts.admin.vertical')
@section('title', __('Reports'))
@section('content')
<acun:page-header :title="__('Reports')" />
@endsection
The page opens in the panel layout with the menu on the left. On a Livewire page, set the same layout with #[Layout('layouts.admin.vertical')].
Choosing a layout
| Layout | When |
|---|---|
layouts.admin.vertical |
Vertical menu on the left; for most admin panels. |
layouts.admin.horizontal |
Menu in the top bar; for apps with only a few main sections. |
layouts.admin.blank |
No menu: sign-in, registration, password, error and maintenance screens. |
The theme's layout files
resources/views/layouts/admin/
├── vertical.blade.php vertical layout: <acun:layout.sidebar>
├── horizontal.blade.php horizontal layout: <acun:layout.horizontal>
├── blank.blade.php layout without a menu: <acun:layout.blank>
└── sections/
├── sidebar.blade.php logo and menu
├── navbar.blade.php search, language switcher, user menu
└── footer.blade.php footer
Each layout file calls one of the package's layout components. It pulls in the section files with @include, and the sections fill the component's slots. Both panel layouts share the same sections. To change the logo, the user menu or the footer, edit the matching section file.
In short, vertical.blade.php looks like this:
<acun:layout.sidebar customizer brand-name="Acunsoft" :home-url="route('dashboard')" :title="$documentTitle">
<acun:slot:head>@vite(['resources/css/acun-ui.css', 'resources/js/acun-ui.js', 'resources/js/app.js'])</acun:slot:head>
@include('layouts.admin.sections.sidebar', ['horizontal' => false])
@include('layouts.admin.sections.navbar')
<acun:slot:footer>@include('layouts.admin.sections.footer')</acun:slot:footer>
@hasSection('content')
@yield('content')
@else
{{ $slot ?? '' }}
@endif
</acun:layout.sidebar>
@yield('content') places a Blade page and $slot places a Livewire page. The top of the file prepares the menu (Menu::fromFile('main')) and the tab title ($documentTitle). The title comes from the page's @section('title'), or else from the #[Title] key; the layout translates the key with __(). You can change the suffix of the title in the $documentTitle line.
The horizontal layout uses the stacked prop: the menu gets its own row below the logo and the search. The footer is drawn with the acun:footer component: the text on the left goes in the default slot, and the links go in the links slot.
Slots
| Slot | What it shows |
|---|---|
head |
Extra <head> contents: @vite, fonts, favicon. |
logo |
The logo. Without it, the brand name is shown as text. |
wordmark |
Vertical layout: the lettering next to the logo; hidden while the menu is collapsed. |
nav |
The menu; in the vertical layout it's also used in the mobile drawer. |
mobileNav |
Horizontal layout: the menu in the mobile drawer; defaults to nav. |
heading |
Vertical layout: the title on the left of the top bar. |
search |
The search in the top bar, e.g. acun:command-palette. |
actions |
Buttons before the theme toggle, e.g. acun:locale-switcher. |
userMenu |
The right side of the top bar: acun:user-menu. |
drawerFooter |
The bottom of the mobile drawer, e.g. a sign-out link. |
footer |
Below the page content, e.g. acun:footer. |
| default | The page content. |
Without a logo, the collapsed menu shows the first letter of the brand name in a small primary-color square.
Common props
| Prop | What it does |
|---|---|
title |
The title in the browser tab. |
brand-name, home-url |
The brand name and the logo's link. |
brand-tagline |
Vertical layout: a short line below the brand name. |
customizer |
Enables the appearance panel on the right edge. |
theme-toggle |
The theme button in the top bar; on by default. |
page-title |
Vertical layout: a short page title instead of the heading slot. |
drawer-title |
The mobile drawer's title; defaults to the brand name. |
stacked |
Horizontal layout: puts the menu on its own row. |
preference-url |
The URL that saves appearance preferences; see Advanced. |
The appearance props (theme, brand-color, skin, semi-dark, sidebar-mode, navbar-mode) are on the Customization page. The horizontal layout doesn't accept brand-tagline, page-title, semi-dark, sidebar-mode or navbar-mode.
Sign-in and error pages
Pages without a menu use the layouts.admin.blank layout. An error screen is drawn with acun:message-panel:
@extends('layouts.admin.blank')
@section('title', __('Page not found'))
@section('content')
<acun:message-panel code="404" :heading="__('Page not found')" :description="__('The page you are looking for may have been moved or deleted.')">
<acun:slot:action><acun:button :href="route('dashboard')">{{ __('Back to dashboard') }}</acun:button></acun:slot:action>
</acun:message-panel>
@endsection
acun:message-panel takes these props: code, heading, description. Its slots are media (a drawing above the code), action (buttons) and illustration (below the buttons). For Laravel's error pages, put the same code in files such as resources/views/errors/404.blade.php.
A sign-in screen is drawn with acun:auth-panel:
<acun:auth-panel variant="basic" :home-url="route('home')">
<acun:slot:logo><img src="/logo.svg" alt="Acme" class="h-8"></acun:slot:logo>
<acun:auth-header :title="__('Welcome')" :description="__('Sign in to your account.')" />
…form…
</acun:auth-panel>
variant="cover" (the default) shows an illustration on the left and the form on the right. Replace the illustration with the illustration slot. variant="basic" shows the form in a single centered card; without a logo, the application's name is shown. In the theme, the sign-in, registration and password pages wrap the panel in the resources/views/components/auth-screen.blade.php component, which holds the logo and the illustration.
Acun UI provides only the look of these screens; it doesn't install an authentication package. You build the logic, routes and model for sign-in, registration and password reset yourself: with your own controllers or Livewire components, or with a package such as Laravel Fortify, Breeze or Sanctum. Draw the form fields and messages with acun:input, acun:checkbox and acun:auth-session-status.
Page scrolling
In the panel layouts, the page itself (the window) scrolls; there's no nested scroll container. The sidebar is fixed and scrolls on its own. With navbar-mode="fixed", the top bar sticks to the top of the page; in the horizontal layout it always sticks. As content passes beneath it, a blurred strip around the bar softens the content. In static mode, the top bar scrolls with the page.
As a result, keyboard scrolling works right away and mobile browsers can hide the address bar. The scroll position is restored when you go back, and libraries that listen for window scrolling work without issues. Overscroll bounce at the edges of the page is off for mouse and trackpad. On touch screens, the phone's own behavior (pull to refresh) is kept. In-page links (#section) land below the sticky top bar. While a modal, a drawer or the command palette is open, the page behind it is locked.
Advanced
Saving preferences to the account
When the user changes the theme, the primary color or the menu width, the value is applied right away and stored in the browser. If you pass preference-url, the value is also sent to that URL as JSON: { "theme": "dark" }, { "sidebar": "collapsed" }, { "brand": "blue" }, { "navbar": "static" }, { "skin": "bordered" }, { "semiDark": true }.
<acun:layout.sidebar :theme="auth()->user()?->theme" :preference-url="route('preferences.update')" …>
You write the route and the code that saves the value. If you store the values in the users table and pass them back to the layout props, the preferences follow the user across devices. A value passed to the layout takes precedence over the browser's. For a preference left as null, the browser's value is used, or the default if there is none. So pass a preference that hasn't been saved yet as null, not with a fallback such as ?? 'fixed'.
A scroll lock for your own dialog
While a dialog you wrote yourself is open, stop the page behind it with the same lock. The lock keeps a count: with nested dialogs, it's released when the last one closes.
AcunUI.scrollLock.lock(); // when the dialog opens
AcunUI.scrollLock.unlock(); // when it closes
For the full dialog behavior, register the dialog with the overlay manager. AcunUI.overlays.open(element, { close }) sets up all of these at once: the scroll lock, closing with Esc, keeping Tab inside the dialog (trap) and returning focus on close. AcunUI.overlays.close(element) undoes all of it. Esc and Tab go only to the most recently opened dialog, and a dialog opened on top appears on top.
const dialog = document.querySelector('#help-dialog');
function openDialog() {
dialog.hidden = false;
AcunUI.overlays.open(dialog, { close: closeDialog, trap: dialog });
AcunUI.focus.first(dialog); // moves focus to the first focusable element
}
function closeDialog() {
AcunUI.overlays.close(dialog);
dialog.hidden = true;
}
The package's layout components
The theme's layout files use these components. You can also use them directly when you write a layout of your own:
acun:layout.sidebarandacun:layout.horizontal: the panel layouts. They add the notification area and, when PRO is installed, the unsaved-changes dialog themselves.acun:layout.blank: only the document. It has the<head>(with the theme and color preferences), the notification area and the Livewire scripts, but no top bar or menu. Attributes given to the component go on the<body>tag. Its props aretitle,themeandbrand-color.acun:layout.authandacun:layout.message:acun:auth-panelandacun:message-panelinside the blank layout. They take the panel's props and slots, plus thetitle,themeandbrand-colorprops and theheadslot.acun:layout.head: put it inside<head>if you write a layout entirely of your own. It applies the theme preference before the first paint, so there's no white flash while the page opens.
Every layout prints @stack('page-scripts') at the end. The old name window.acunScrollLock still works but writes a warning to the console; use AcunUI.scrollLock.
Learn more
- Your first page: add a new page to a layout.
- Menu: the menu file and search.
- Customization: color, dark mode and appearance options.
- Adding to an existing project: set up a layout without the theme.