EN
Getting started

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.sidebar and acun: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 are title, theme and brand-color.
  • acun:layout.auth and acun:layout.message: acun:auth-panel and acun:message-panel inside the blank layout. They take the panel's props and slots, plus the title, theme and brand-color props and the head slot.
  • 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

Acun UIDesigned for people.