Skip to main content

Documentation

No results found.
Features

Popup Rows (page-embedded announcement modals)

WebProCMS lets editors add popup/modal announcements to any page straight from the page builder — promotional offers, seasonal announcements, event reminders, newsletter pitches. A popup is just another design-library row: it's inserted fro...

WebProCMS lets editors add popup/modal announcements to any page straight from the page builder — promotional offers, seasonal announcements, event reminders, newsletter pitches. A popup is just another design-library row: it's inserted from the "Popups" category in the add-row picker, edited with the same sidebar cards as every other row (headings, rich text, buttons, images), and rendered as a full-screen overlay on the live site with configurable timing, frequency capping, and enter/exit animations.


What editors get

  • A "Popups" category in the add-row picker with ready-made templates:
    • Popup - Promo Split — photo on the left, headline + supporting text + CTA button on the right (the classic "we now offer X" announcement).
    • Popup - Image Banner — full-bleed background image behind a dark scrim with centered headline and a CTA pair.
    • Popup - Announcement — simple centered card with an icon badge, headline, rich-text body, and two buttons.
  • Normal row editing. The popup's contents are standard design-library components (x-dl.heading, x-dl.subheadline rich text, x-dl.buttons, x-dl.link, x-dl.image, x-dl.icon), so everything — copy, colors, buttons, images — edits exactly like any other row, including AI text/image generation and translations.
  • Popup behaviour settings on the modal's sidebar card:
    • Enable Popup — master toggle.
    • Show Frequency — Once per day (default), Once ever, or Every visit.
    • Delay (seconds) — how long after page load before the popup opens (default 2s).
    • Enter / Exit Animation — Zoom, Fade, Fade Up, Fade Down, or None. Enter runs at 500ms, exit at a snappier 300ms.
    • Background Image — optional cover-fit image painted behind the panel content.
    • Overlay / Panel / Close Button classes — full Tailwind control over the backdrop scrim, the dialog surface, and the dismiss button.
    • Element ID / Custom Attributes — the standard Advanced fields.
  • In-editor preview. In the page editor and the design-library preview the popup renders statically in-flow (visible panel over its overlay color) so it can be seen and edited without being hijacked by the live trigger logic. On the published page the row occupies no space until it opens.

Frequency capping ("show once per day")

The default behaviour matches the common ad-popup convention: a visitor sees the popup the first time they open that page each day, and not again on subsequent visits to the page that day — closing it or navigating away doesn't bring it back until the visitor's local midnight rolls over.

  • The stamp is stored in the visitor's browser (localStorage), keyed per page path + row instance (dl-popup:{path}:{slug}:{prefix}), so two different popups — or the same popup on two different pages — track independently.
  • "Once per day" uses the visitor's local date, not the server's, so the reset happens at their midnight.
  • "Once ever" shows a single time per browser; "Every visit" shows on every page load (useful while designing, or for must-see notices).
  • The stamp is written when the popup opens (not when it's dismissed), so a reload mid-popup doesn't re-trigger it.
  • Private-browsing visitors with storage blocked simply see the popup each visit — the failure mode is graceful.

Caching architecture (why it's all client-side)

The cached page HTML never varies per visitor — ResponseCache stores one body per URL. The popup therefore ships in every cached response as a hidden fixed-position element carrying its behaviour as data attributes (data-popup-frequency, data-popup-delay, data-popup-enter, data-popup-exit). All per-visitor decisions (has this visitor seen it today? when should it open?) happen in resources/js/dl-popup.js, which the public bundle lazy-imports only when a [data-dl-popup] element is present on the page — pages without popups pay zero JS cost.

Accessibility

  • The live popup renders with role="dialog", aria-modal="true", and an accessible label.
  • Opening moves focus to the panel; closing restores focus to the previously-focused element.
  • Escape closes; clicking the backdrop or the X button closes; Tab focus is contained inside the open dialog.
  • Enter/exit animations are skipped for visitors with prefers-reduced-motion: reduce.
  • Page scroll is locked while the popup is open and always released on close or navigation.

Architecture (for developers)

Piece File Role
Component schema app/Support/DlSchemas/Modal.php Field registration + enterPresets() / exitPresets() (tailwindcss-animate class maps) + frequencyOptions()
Blade component resources/views/components/dl/modal.blade.php Renders the live overlay (hidden + data-attribute contract) or the static preview branch when editor_preview() / design_library_preview()
Runtime resources/js/dl-popup.js Frequency check, delayed open, animations, close paths, focus management, scroll lock
Lazy boot resources/js/public.js Gated on [data-dl-popup]; re-runs on livewire:navigated
Row templates resources/design-library/rows/popups/ The Popups category examples
Category app/Enums/RowCategory.php Popups case (always available, no feature gate)
Tests tests/Feature/DesignLibrary/ModalComponentTest.php Schema, live contract, overrides, toggle-off, preview branch, all three templates

Implementation notes:

  • Row shape. A popup row still uses <x-dl.section> as its outer wrapper (required by the parser/editor), but with empty field-section-classes / field-container-classes — on the live page the section is a zero-height in-flow element containing the fixed overlay.
  • Editor drill-in. x-dl.modal is in RowBladeSurgery::RECURSE_INTO_SLUGS, so its children get their own @dl-item markers and sidebar cards; the modal itself is a card holding the behaviour fields.
  • Structural vs. design classes. Positioning (fixed inset-0 z-[90] hidden items-center justify-center) is hardcoded in the component — it's functional, not design, and swapping it per-branch is what makes the preview render statically. Visual surfaces (overlay scrim, panel, close button) are editable class fields.
  • Animation classes come from Modal::enterPresets() / exitPresets() and are force-included in resources/css/public.css via @source inline(...) (the animate-out family isn't referenced in any scanned file).
  • Select field types popup_frequency and popup_animation are wired in alpineDropdownOptions (editor-state.js) and both type lists in content-field-alpine.blade.php — the same pattern as video_source.
  • No consent gating needed. The popup is first-party markup with no third-party scripts or cookies; localStorage frequency stamps are functional storage, not tracking.