EN
Getting started

CSS, JS and build

This page shows you how to build the CSS and JavaScript, and how Acun UI loads these files into your pages.

Building

When you change the design, rebuild the CSS and JavaScript:

npm install        # first time only: installs the Vite and Tailwind build tools
npm run build      # builds for production (public/build)

The compiled files come ready in the theme's zip. Rebuild when:

  • you changed the colors or the @theme settings,
  • you wrote new Tailwind classes in Blade files,
  • you changed files under resources/js,
  • you updated the Acun UI packages.

npm install installs only the build tools. Acun UI itself doesn't come from npm: the "@acunsoft/acun-ui-core": "file:packages/acun-ui/acun-ui-core" line in package.json uses the folder from the zip. What to do before uploading to a server is on the Deployment page.

Dev server

While developing, run Vite's dev server instead of rebuilding after every change:

npm run dev

CSS and JavaScript changes show up instantly. When a Blade file or the menu changes, the page reloads by itself. composer run dev starts the server, the queue and Vite together.

When you stop the dev server, pages go back to using the compiled files in public/build.

Files loaded on every page

The built-in layouts load three files in the <head> of every page:

@vite(['resources/css/acun-ui.css', 'resources/js/acun-ui.js', 'resources/js/app.js'])
File Contains
resources/css/acun-ui.css Tailwind, the Acun UI theme and the PRO styles
resources/js/acun-ui.js The Acun UI JavaScript every page needs
resources/js/app.js Your application's own code; chart, map and date registrations

The same three files are in the input list in vite.config.js. Load them in this order in your own layout too. In an existing project, the CSS file is usually named resources/css/app.css; it works the same way.

The CSS file

The CSS file brings together Tailwind, the Acun UI theme and the component styles:

@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@import '@acunsoft/acun-ui-core';
@import '../../vendor/acunsoft/acun-ui-pro/resources/css/acun-ui-pro.css';

@source '../../vendor/acunsoft/acun-ui-free/resources/views';
@source '../../vendor/acunsoft/acun-ui-pro/resources/views';
  • @import '@acunsoft/acun-ui-core' brings in the theme, the layout and the menu.
  • acun-ui-pro.css adds the DataTable and form modal styles.
  • The @source lines make Tailwind scan the packages' Blade files. Without them, the components' classes don't make it into the build and the components appear unstyled.

In the full version, the file also imports front.css: the font, corner and surface rules of the storefront themes. To change the colors and the look, see the Customization page.

JavaScript layers

Acun UI's JavaScript is split into layers, so each page downloads only what it needs.

When What loads
On every page acun-ui.js: theme, modal, drawer, dropdown, tabs, focus, scroll lock, events, Livewire fixes and the unsaved-changes guard from PRO
On every page app.js: your application's own code
Only when needed DataTable, combobox, file upload and form modal; the chart, map and date libraries
When a page has its own code That page's own file, added with @stack('page-scripts')

resources/js/acun-ui.js is two lines:

import '@acunsoft/acun-ui-core';
import '@acunsoft/acun-ui-pro';

Because Acun UI lives in a separate file, the browser doesn't download it again when your application's code changes. app.js doesn't import Acun UI; otherwise Vite would extract a shared chunk for the two entries. That's why PRO's registration functions are under window.AcunUI.pro. Alpine ships with Livewire, so you don't need to install it separately.

Without import '@acunsoft/acun-ui-core', drawers and modals don't open and the appearance settings don't work. Every page that uses a panel layout needs it.

@acunsoft/acun-ui-pro isn't an npm package. The alias in vite.config.js finds it in the Composer package:

resolve: {
    alias: { '@acunsoft/acun-ui-pro': path.resolve(import.meta.dirname, 'vendor/acunsoft/acun-ui-pro/resources/js/index.js') },
},

Heavy components only when needed

The JavaScript of large components downloads only when that component is on the page. For example, the browser side of DataTable (export, full screen, fixed header and columns), the combobox, file upload and form modal are separate chunks.

<!-- the HTML acun:combobox renders on the server -->
<div x-data="uiCombobox" data-acun-component="combobox">…</div>

The component puts a data-acun-component="…" marker in the server-rendered HTML. The first time an element with that marker appears on the page, Acun UI downloads the component's chunk once. Alpine initializes the element when the chunk arrives.

  • This works the same way on first load, on wire:navigate transitions, for elements Livewire adds later and inside nested Livewire components. There is no separate page scan.
  • Five comboboxes on one page download the chunk once.
  • Until the chunk arrives, the component's server-rendered HTML is visible.
  • A DataTable without browser-side actions downloads no JavaScript at all.
  • If a chunk can't be downloaded, the console shows [Acun UI] The JavaScript of the "combobox" component could not be loaded.

Downloading a chunk ahead of time

You can download a chunk ahead of time, for example for a dialog the user is about to open:

AcunUI.load('combobox');

Adding your own component

To add your own heavy component to the same scheme, define it under a name:

window.AcunUI.components.define('editor', () => import('./components/editor.js').then((module) => module.register(window.Alpine)));
<div data-acun-component="editor" x-data="editor(@js($settings))">…</div>

PRO's registerCombobox, registerFileUpload, registerDataTable, registerDataTableFixed and registerFormModal functions download the component's chunk first and return a Promise. You can keep calling them as before. For the unsaved-changes manager's API: const { initDirtyForms } = await loadDirtyForms();.

Page-specific code

Load JavaScript that only one page needs on that page:

@push('page-scripts')
    @vite('resources/js/pages/report.js')
@endpush

The built-in layouts output @stack('page-scripts') before </body>. Also add the file to the input list in vite.config.js.

Put reusable behavior (tables, modals) in a component, not here; page code should only do that page's own work. When wire:navigate returns to the same page, the module doesn't run again. Put code that must run on every visit inside document.addEventListener('livewire:navigated', …).

Charts, maps and date ranges

The chart, map and date range components use external libraries: ApexCharts, Leaflet and flatpickr. Your application hands these libraries to Acun UI. In resources/js/app.js:

document.addEventListener('livewire:init', () => {
    const { lazyLibrary, registerChart, registerDateRangePicker, registerMap } = window.AcunUI.pro;

    registerChart(window.Alpine, lazyLibrary(() => import('apexcharts')));

    registerMap(window.Alpine, lazyLibrary(() => import('leaflet/dist/leaflet.css').then(() => import('leaflet'))));

    // The calendar follows the page language: Turkish month and day names on a Turkish page.
    registerDateRangePicker(window.Alpine, lazyLibrary(async () => {
        const turkish = document.documentElement.lang.startsWith('tr');
        const [{ default: flatpickr }, locale] = await Promise.all([
            import('flatpickr'),
            turkish ? import('flatpickr/dist/l10n/tr.js') : Promise.resolve(null),
            import('flatpickr/dist/flatpickr.min.css'),
        ]);
        if (locale) flatpickr.localize(locale.Turkish);

        return flatpickr;
    }));
});

lazyLibrary keeps the library out of the application's JavaScript. The library downloads as a separate chunk, together with its styles, only on a page that contains the component and only the first time it's used. ApexCharts alone is about 530 kB; pages without a chart never download it.

You can also pass the library directly: registerChart(window.Alpine, ApexCharts). The library then ships with the application's JavaScript on every page.

To give the chunks names (apexcharts-*.js, leaflet-*.js, flatpickr-*.js), add this to vite.config.js:

build: {
    rolldownOptions: {
        output: {
            codeSplitting: {
                groups: [
                    { name: 'apexcharts', test: /node_modules[\\/]apexcharts[\\/]/ },
                    { name: 'leaflet', test: /node_modules[\\/]leaflet[\\/]/ },
                    { name: 'flatpickr', test: /node_modules[\\/]flatpickr[\\/]/ },
                ],
            },
        },
    },
    // apexcharts-*.js is the library itself and is downloaded only on pages with a chart.
    chunkSizeWarningLimit: 600,
},

Warning

By default, the map uses OpenStreetMap's free tile server. That server doesn't allow heavy production traffic. In production, pass your own tile provider with registerMap(Alpine, L, { tileUrl, attribution }).

Fonts

The theme uses Public Sans for text and DM Mono for code and numeric labels. Load both from vite.config.js:

import { bunny } from 'laravel-vite-plugin/fonts';
import { woff2Only } from '@acunsoft/acun-ui-core/vite';

plugins: [
    laravel({
        input: ['resources/css/acun-ui.css', 'resources/js/acun-ui.js', 'resources/js/app.js'],
        fonts: [
            bunny('Public Sans', { weights: [400, 500, 600, 700], subsets: ['latin', 'latin-ext'], preload: [{ weight: 400 }, { weight: 600 }] }),
            bunny('DM Mono', { weights: [400, 500], subsets: ['latin', 'latin-ext'], preload: false }),
        ],
    }),
    woff2Only(),
    tailwindcss(),
],

Then add the font tags to the layout's head slot:

@acunFonts(['public-sans', 'dm-mono'])

The latin-ext subset contains the Turkish letters (ş, ğ, İ, ı).

woff2Only

woff2Only() keeps only the WOFF2 files in the font build; add it after laravel(). Font providers send every face as both WOFF2 and WOFF. The plugin writes them as two separate @font-face rules with identical descriptors and puts WOFF last. Since the browser uses the last one, it downloads the larger WOFF files and the preloaded WOFF2 files go to waste. Every browser Acun UI supports can read WOFF2. woff2Only() removes the WOFF files, their rules and their manifest entries.

Preloading

preload preloads only the weights needed immediately on the first screen: 400 for body text, 600 for headings and buttons. Each weight is two files (latin and latin-ext), so the page gets four font preloads. The other weights and DM Mono load when they're used (font-display: swap).

Without preload, every weight and subset is preloaded: 12 files in this configuration. The browser doesn't use most of them on the first screen and warns "preloaded but not used".

@acunFonts

@acunFonts prints the same preload tags as Laravel's @fonts directive. The difference: it doesn't write the @font-face rules inline as a <style> on every page; it links the fonts-*.css file the plugin builds. The browser caches that file.

If you define several font families, each layout can list only its own. The full version's storefront themes do this:

{{-- layouts/front/theme-3.blade.php: Fraunces for headings --}}
@acunFonts(['public-sans', 'fraunces', 'dm-mono'])

The preload tags are printed only for those families, and the linked file stays the same. The browser doesn't download a font the page doesn't use.

  • While the Vite dev server is running, @acunFonts behaves exactly like @fonts.
  • Before Laravel 13, @acunFonts prints nothing; load the fonts with your own CSS instead.
  • If you use your own font files, change the --font-sans and --font-mono values in @theme.

Loading without Vite

If you don't use Vite, load the compiled copies of the JavaScript that ship with the packages using the @acunUiScripts directive. In the panel layouts, put it in the head slot:

<acun:layout.sidebar brand-name="My App">
    <acun:slot:head>
        @acunUiScripts
    </acun:slot:head>

    {{ $slot }}
</acun:layout.sidebar>

The directive lives in the free acunsoft/acun-ui-free package. It outputs Acun UI's shared JavaScript (acun-ui.js) first, then the PRO JavaScript (acun-ui-pro.js) if the PRO package is installed. In your own layout, you can put it inside <head> or before </body>. The tags are deferred and run before Livewire starts Alpine; you don't need to add anything else.

  • Files: they're in the packages' dist/ folders (acun-ui-free/dist/acun-ui.js, acun-ui-pro/dist/acun-ui-pro.js). No build step is needed. The packages serve them from /acun-ui/acun-ui.js?id=<content hash> and /acun-ui/acun-ui-pro.js?id=<content hash> with a one-year cache. When a file changes, its URL changes too.
  • URL settings: they're in config/acun-ui.php. For the shared JavaScript, assets.prefix is the URL prefix (default acun-ui) and assets.url is a CDN folder that holds the files. For PRO, the same settings are called pro.assets.prefix and pro.assets.url; here url is the file's full URL. See Configuration.
  • Serving from the web server: publish the files: php artisan vendor:publish --tag=acun-ui-assets (public/vendor/acun-ui), and for PRO --tag=acun-ui-pro-assets (public/vendor/acun-ui-pro). The directive then uses these copies. After updating the package, republish with --force. An outdated copy of the shared JavaScript is not used; the package's own file is loaded instead. For an outdated copy of PRO, a warning appears in the console.
  • Charts, maps and date ranges: if you load ApexCharts, Leaflet and flatpickr with <script> tags (without async), acun:chart, acun:map and the date range picker register themselves. flatpickr's language is picked from the page's lang attribute. To register with your own settings, call window.AcunUI.pro.registerMap(window.Alpine, L, { tileUrl, attribution }) inside livewire:init.
  • Together with Vite: if the application's JavaScript already loads Acun UI with Vite (import '@acunsoft/acun-ui-core', import '@acunsoft/acun-ui-pro'), the directive's files do nothing. Still, pick one approach or the other.

The theme file

The theme file applies the user's theme, primary color and menu choices before the page is drawn on screen, so the page doesn't flicker between colors while it opens. acun:layout.head writes it into <head> itself (the built-in layouts already use this component):

<script type="application/json" data-acun-ui-config>{…}</script>
<script src="/acun-ui/acun-ui-boot.js?id=…" data-acun-ui-boot data-navigate-track></script>

This file isn't deferred, because it must run before the first paint. It ships with the package, and because its URL carries a content hash (?id=…), the browser caches it for a year. The layout writes no inline JavaScript; settings arrive purely as data in the data-acun-ui-config tag.

Content Security Policy

If your site uses a Content Security Policy (CSP), give Acun UI's script tags a nonce:

@acunUiScripts(['nonce' => $nonce])
<acun:layout.head :nonce="$nonce">…</acun:layout.head>

Without a nonce, both use Vite::cspNonce(). The built-in layouts take the nonce from there, so in Laravel it's enough to generate one with Vite::useCspNonce().

If the shared JavaScript isn't loaded

On a panel page without import '@acunsoft/acun-ui-core' or @acunUiScripts, the theme file writes a single error to the browser console:

[Acun UI] JavaScript is not loaded: add "import '@acunsoft/acun-ui-core';" to resources/js/app.js and build the assets (npm run build), or add @acunUiScripts to your layout. Drawers, modals and the theme settings will not work.

The page opens with its saved appearance and no other error appears. But drawers and modals don't open, and the theme and menu settings can't be changed. The fix: add the import '@acunsoft/acun-ui-core' line to your JavaScript entry (resources/js/acun-ui.js in the theme) and build, or add @acunUiScripts to the layout.

If the PRO JavaScript isn't loaded

Components that need the PRO JavaScript add a small check to the page once. These components are acun:combobox, acun:file-upload, acun:form-modal, acun:form dirty-check, and any DataTable with browser-side actions, a fixed header or fixed columns. If the PRO JavaScript isn't loaded, the console shows this error:

[Acun UI PRO] JavaScript is not loaded: add "import '@acunsoft/acun-ui-pro';" to resources/js/app.js and build the assets (npm run build), or add @acunUiScripts to your layout. Components that will not work: acun:combobox (1), acun:data-table (1).
  • On the first page load, comboboxes and file upload fields stay faded and unclickable. The rest of the page works.
  • In a DataTable, search, filters and pagination keep working. Browser-side actions such as export, copy and full screen don't.
  • If @acunUiScripts is on the page but the file didn't load, the error starts with "The JavaScript file could not be loaded (…)".

Details: Frequently asked questions.

Learn more

Acun UIDesigned for people.