Skip to main content
A theme is a folder. Drop it in resources/js/pages/themes/, pick it in the admin settings, and your versions of the pages take over. You never have to copy the whole UI.
This page covers the client theme system, the one that restyles the dashboard, market, tickets and auth screens. It is a different thing from portal themes, which change the public landing page and are managed as database records under Customization. See Portal themes vs client themes.

How it works

1

You create a folder

resources/js/pages/themes/aurora/. The folder name is the theme name.
2

The admin settings page finds it

DezerX scans the themes directory on every load, so there is nothing to register.
3

An admin activates it

That writes the active_theme setting, which is shared with the frontend on every request.
4

Inertia resolves your file first

For each page it tries your theme, then falls back to default.
Resolution order
A theme with one file is valid. quanta ships five auth pages and nothing else, and every other screen quietly uses the default.

Create one

1

Make the folder

Use lowercase with hyphens. The admin label is derived from the folder name, so aurora shows as “Aurora” and neon-dark shows as “Neon Dark”.
2

Copy a page you want to change

Starting from the default copy means you inherit the props, the form wiring and the translation keys, and you only have to change markup.
3

Build and activate

Then go to Admin, Settings, Client Theme and pick Aurora.
Vite discovers theme files with a glob at build time, so a new folder needs a rebuild (or a restart of npm run dev) before it appears. Adding files to a theme that already exists is picked up by the dev server automatically.

What you can override

Live in {theme}/auth/. The filename must match exactly.
Admin pages are not themeable. They live in resources/js/pages/admin/ and are shared by every theme.

Layouts come for free

A UI/ page does not need to declare a layout. If yours does not set one, the resolver wraps it in AuthenticatedLayout, which gives you the sidebar, navbar and footer.
resources/js/pages/themes/aurora/UI/dashboard.tsx
If you want a page to render without the standard chrome, set a layout explicitly:
Auth pages are never wrapped. They own their whole screen, which is why quanta can restyle sign in completely without touching anything else.

Overriding components

Copying pages gets expensive if all you want is a different card or button. Most shared pieces in the default theme are wrapped in withThemeOverride, so a file with a matching path replaces them everywhere.
1

Create the file at the same path

2

Export the same name

A default export works too. The named export is tried first.
Your override receives the same props as the original, and callers still pass them. Read the default implementation before you replace it, or you will silently drop behaviour like loading states or permission checks.
Path is relative to {theme}/components/. Export name matters.
Overrides are only consulted when active_theme is not default, and the loader deliberately excludes themes/default/components/** from the on-demand bundle.That keeps the default path free of extra work, and means a theme’s component files are only fetched when that theme is active.

Colours and spacing

Before writing custom CSS, reach for the existing variables. They are what the admin Colors page edits, so anything built on them stays in sync when an operator retunes the palette.
Button helpers: button-primary, button-secondary, button-danger, button-warning, button-text, button-link.
Counting usage across the default theme: text-primary, border-primary, text-secondary, bg-secondary and bg-primary do most of the work. Match those and your theme will respond to light and dark mode and to operator colour changes without extra effort.
Hardcoding hex values is the most common theme mistake. It looks fine in the mode you built in, then breaks in the other one and ignores whatever the operator sets under Customization, Colors.

What your pages receive

Theme pages are ordinary Inertia pages, so props come from the controller that rendered them. Two things are always available.
settings is an array of every non-sensitive row from the settings table, which is also where active_theme itself lives.
Copy the default page you are replacing and keep its interface PageProps. That is the quickest way to see exactly what the controller sends without reading the PHP.

Things worth reusing

You are not limited to your own folder. These are shared and safe to import from any theme.
Run any HTML or SVG that came from a database setting through @/lib/sanitize before rendering it. Operators can paste custom markup into settings, and a theme that renders it raw is an XSS hole.

Test it

1

Activate the theme

Admin, Settings, Client Theme.
2

Walk every page you overrode

Plus one you did not, to confirm the fallback still works.
3

Toggle light and dark mode

Then change a colour under Customization, Colors and reload.
4

Check narrow screens

The default components are responsive. Overrides often are not.
5

Sign out and back in

Auth pages render without a session, so a prop you assumed exists may be null.
6

Switch back to Default

Confirm nothing you changed leaked into the default theme.

Portal themes vs client themes

Two separate systems with similar names.

Client themes

This page. Folders in resources/js/pages/themes/, selected with the active_theme setting. Restyle the dashboard, market, tickets and auth screens.

Portal themes

Database records (portal_themes) managed under Admin, Customization. Each points at a component path such as resources/js/pages/Portal/Default/welcome.tsx and changes the public landing page.
Activating one has no effect on the other.

When it does not work