The Templates page surfaces a Quick Edit button on every installed header. Clicking it opens an inline panel — uppercase nav links, contained vs. full-width, button style, background color, opacity — that writes the changes directly into the header's blade file. The page editor is never opened. The change applies to whichever header variant (light or dark) the editor is editing, and because every page on the site uses the active header, all pages reflect the change at once.
The problem
The most common header changes — make the nav links uppercase, switch the CTA from Primary to Outline, swap the background from white to primary, drop opacity on a transparent header — are also the most disruptive ones to make through the page editor. The editor opens the full row, the user has to find the right <x-dl.*> field, type a class change, save, and watch a rebuild. That's three clicks and a rebuild for what should be a switch flip. Worse, the header is shared across every page on the site, so the editor doesn't even need to be on a particular page to make the change — the page-editor entry point is incidental at best.
The fix
Quick Edit is a small set of switch / select controls that read the current state out of the installed header partial and write changes back surgically. Each control corresponds to a specific <x-dl.*> attribute on a specific tag inside the header — the rest of the file is left alone. Conflicting ContentOverride rows for the touched keys are deleted on update so the file change is the source of truth after a quick edit.
HeaderQuickEditService is the surgical editor. The dashboard hosts the panel; the service does the parsing and rewriting.
What the panel exposes
The panel renders one row of fields. Controls only appear when the underlying header has the structural element they target — a header without a <x-dl.nav prefix="main_nav"> won't show the Uppercase Links switch, a header with no max-w-* or inset-x-* token anywhere won't show the Contained switch.
| Control | Target tags | What it does |
|---|---|---|
| Uppercase Links | <x-dl.nav prefix="main_nav">, <x-dl.nav prefix="mobile_nav">, <x-dl.link prefix="primary_cta">, <x-dl.link prefix="mobile_cta"> |
Adds or removes the uppercase class on the nav field-item-classes and the CTA field-classes. Both desktop and mobile prefixes are kept in sync. |
| Contained | <x-dl.section> (field-section-classes and field-container-classes), every <x-dl.wrapper>'s field-classes |
Toggles between width-constrained and full-bleed using the snapshot pattern below. |
| Button Style | <x-dl.link prefix="primary_cta"> and <x-dl.link prefix="mobile_cta"> |
Sets field-btn-style to one of the global button utility tokens (btn-primary, btn-secondary, btn-outline, etc.). Both prefixes are kept in sync. |
| Background Color | <x-dl.section> field-section-classes |
Replaces the existing bg-{token} (and any /N opacity suffix) with a new token. dark:bg-* classes are left alone. |
| Opacity (%) | Same as Background Color | Sets the /N suffix on the bg token (bg-white/80). Empty / 100 strips the suffix. |
The supported background tokens are primary, secondary, tone, white, black, transparent — exposed as HeaderQuickEditService::SUPPORTED_BG_TOKENS. Anything outside that list is rejected.
The contained snapshot pattern
The Contained toggle is the trickiest of the controls because there are two distinct layout patterns in the header library and the toggle has to handle both without losing the original tokens.
| Pattern | "Contained" looks like | "Full-width" looks like |
|---|---|---|
| A/B (max-w) | max-w-container mx-auto on the section's container scope |
qe-was-max-w-container qe-was-mx-auto (renamed; Tailwind ignores them) |
| C (pill / inset) | inset-x-0 max-w-container mx-auto plus a qe-was-inset-x-N snapshot |
The original inset-x-N restored, snapshot tokens stripped |
Going full-width never deletes the original tokens — it renames them with a qe-was- prefix that Tailwind's scanner ignores, so the visual change happens but the original is recoverable. Toggling back restores the snapshot exactly. A header that has no width-constraint scope at all (no max-w-*, no inset-x-* anywhere) reports has_container_scope = false and the Contained switch never appears.
This matters because pill-style and standard fixed headers need different mechanics — a pill is naturally pinned to viewport edges via inset-x-N and "contained" means adding a max-w + center, while a standard header is naturally centered via max-w-* mx-auto and "full-width" means removing them. The same toggle handles both.
Override clearing — file is the source of truth
When Quick Edit writes a change, the service deletes any ContentOverride row for the same row_slug-and-key combination. Without this, an editor who had previously typed text-sm uppercase into the Section editor for main_nav_item_classes would see their override take precedence and the Quick Edit change would silently lose. After a Quick Edit, the file content is canonical for those keys until the editor explicitly overrides them again from the page editor.
The cleared keys per control:
| Control | Cleared override keys |
|---|---|
| Uppercase Links | main_nav_item_classes, mobile_nav_item_classes, primary_cta_classes, mobile_cta_classes |
| Contained | section_classes, section_container_classes |
| Button Style | primary_cta_btn_style, mobile_cta_btn_style |
| Background Color / Opacity | section_classes |
All headers, every page
The header is installed once into resources/views/layouts/partials/header-{slug}.blade.php and the active variant is included by every public page. Quick Edit writes back to that one partial, so a single switch flip propagates to the entire site immediately. The dashboard panel dispatches a header-preview-stale event after each update so the embedded preview re-fetches and reflects the change without a manual refresh.
Where the panel lives
The Quick Edit panel is on Dashboard → Templates. Each installed header (light / dark variants tracked separately) has a "Quick Edit" button alongside the usual "Set Active" / "Edit Header" / "Delete" actions. The panel slides open below the header card; closing it just collapses — there's no Save button because each control writes immediately on change.
The supporting Livewire component is ⚡templates.blade.php, which exposes openQuickEdit($slug), closeQuickEdit(), quickEditSlug, quickEditControls, and quickEditSupports. The Livewire updated* handlers route every property change through (new HeaderQuickEditService)->update($slug, …). The actual UI markup lives in template-header-slot-installed-actions.blade.php.
What lives where
| Path | Purpose |
|---|---|
app/Support/HeaderQuickEditService.php |
The surgical editor — readState($slug) parses the partial, update($slug, $changes) rewrites it. Owns the snapshot pattern, the bg-token regex, the override-clearing logic. |
resources/views/pages/dashboard/⚡templates.blade.php |
The Templates page — hosts the Livewire state for the open quick-edit slug, the controls bag, and the support flags. |
resources/views/pages/dashboard/partials/template-header-slot-installed-actions.blade.php |
The button row + collapsible panel UI. |
tests/Feature/HeaderQuickEditServiceTest.php |
Coverage for readState initial-state parsing, uppercase toggle on both nav prefixes and both CTA prefixes, contained-snapshot reversibility, bg-token / opacity changes. |
Marketing angle
The most common header edits — adding a nav link's uppercase styling, changing the CTA from Primary to Outline, switching the background from white to primary, dialing the transparency on a fixed header — take seconds without opening the page editor. Click Templates, find your active header, click Quick Edit, flip a switch. The change goes live across every page on the site, no rebuild prompt, no design library detour, no class typing. For non-design content changes, the page editor isn't even the fastest path anymore.
Notes
- Quick Edit only sees a change request as "successful" if the file content actually differs after the rewrite. Writing the same value twice is a no-op — no file modification time bump, no preview reload.
- The service refuses to operate on a slug whose partial doesn't exist (
is_file($path)check). BothreadStateandupdatereturn early with no error, no exception, no DB write. - The supported bg tokens are exposed as a public constant (
HeaderQuickEditService::SUPPORTED_BG_TOKENS) so the Templates panel iterates them directly — there's one list, not two. - Per-form button styles (e.g. an inline newsletter signup that should match the header's CTA style) read from a separate
Form::submit_button_stylefield — the Quick Edit Button Style only affects the header's CTA links, not embedded forms.