Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ux Swup Laravel Package

symfony/ux-swup

symfony/ux-swup integrates Swup’s AJAX page transitions into Symfony apps, enabling smooth client-side navigation, animated page loads, and preserved JavaScript state with minimal setup. Ideal for adding modern SPA-like transitions to Twig templates.

View on GitHub
Deep Wiki
Context7

Getting Started

Install. composer require symfony/ux-swup. The Flex recipe registers Symfony\UX\Swup\SwupBundle in config/bundles.php and, if you're on Symfony AssetMapper, adds the swup package to importmap.php (you can confirm with php bin/console importmap:require swup — it's a no-op if present). On Webpack Encore, npm install swup and make sure your webpack.config.js copies the module into the build.

Wire up a container. Swup only works if every page has the same DOM element to replace. In templates/base.html.twig:

<body>
    <main id="swup">
        {% block body %}{% endblock %}
    </main>

    <twig:Swup />
</body>

#swup is the default container. The <twig:Swup /> call renders the Stimulus controller that boots Swup and can appear anywhere in the template (usually right before </body> or in <head> with defer).

First use case. Add a CSS fade on html and confirm in devtools that window.swup exists and that clicking a link swaps <main> without a full reload. That's the whole feature — you're now shipping instant-feeling navigation on top of ordinary Symfony controllers. Where to look first: the Symfony UX Swup docs, then the Swup handbook — the PHP side is thin, the behavior lives in the JS library.

Implementation Patterns

Custom container(s). The component takes containers and options as Twig props, which is how you avoid hardcoding defaults in JS:

<twig:Swup
    containers={{ { header: '#site-header', main: '#swup' } }}
    options={{ { animateHistoryBrowsing: true, animationSelector: '[class*="transition-"]' } }}
/>

A containers map with several selectors is the standard trick for keeping a sticky header/nav and cart badge persistent while swapping the main region.

Partial updates (cart, flash messages). Because you own the container, you can also swap sidecar fragments from a Swup hook instead of from a controller response. Register a hook on page:load and fetch a tiny endpoint that returns just the cart HTML, then outerHTML it into place. This is how people keep counters and "unread" badges fresh without touching the main page swap.

Custom JS that must re-run. DOMContentLoaded fires once. Third-party scripts (analytics snippets, date pickers, tooltips) need to be re-initialized on Swup events:

import Swup from 'swup'; // already loaded by the UX controller

document.addEventListener('swup:page:view', () => reinitWidgets());
// or, on the global instance:
window.swup?.hooks.on('page:load', () => reinitWidgets());

Prefer page:load for DOM-dependent work and page:view for analytics/scroll/focus. Do not rely on document.addEventListener('load', ...) alone — it fires only on the initial hard navigation.

Plugins. Swup's real power is in plugins (@swup/preload-plugin, @swup/head-plugin, @swup/scripts-plugin, @swup/forms-plugin, @swup/scroll-plugin, @swup/accessibility-plugin). Import them via importmap:require (or npm) and pass through the component:

<twig:Swup options={{ { plugins: plugins } }} />

where plugins is exposed by a small controller or inline JS that imports the plugin classes. For anything beyond simple config, subclass the UX Stimulus controller (or write a one-file controller) and instantiate new Swup({ ..., plugins }) yourself — the UX wrapper is a convenience, not a lock-in.

Stimulus integration. Other Stimulus controllers registered on page content are reconnected automatically by Swup only if you wire the @swup/scripts-plugin or re-evaluate scripts on page:load. If your controllers use connect() for one-time DOM setup, they generally work; if they cache references across navigations, guard with an isConnected check.

Forms, CSRF, and POST. Out of the box Swup only intercepts GET navigations. For progressive form submission add @swup/forms-plugin; Symfony's CSRF tokens in rendered forms are replaced on each swap, so re-render the form container, not just the success message.

Opting out per link. data-no-swup on an <a> (or on a whole <form>/element) forces a normal full-page navigation. Use it for file downloads, OAuth redirects, mailto-links, and anything you don't want to be swapped.

Gotchas and Tips

  • Container must exist on every response. If a controller returns a template missing #swup (a bare fragment, an exception page, a login screen), Swup will throw on the next navigation. Keep it in the base layout, or use data-no-swup on links leading to fragment-only pages.
  • <head> is not swapped by default. If you rely on per-page <title>, <meta> descriptions, or per-page CSS, add @swup/head-plugin. Without it, deep-linked titles stay stale and analytics picks up the wrong page title.
  • Scripts in swapped content don't execute. Browsers strip <script> from innerHTML. Use @swup/scripts-plugin or move per-page JS into Stimulus controllers/data attributes so a hook can invoke it.
  • Do not combine with Symfony UX Turbo. Both intercept link clicks and will fight over history, forms, and DOM replacement. Pick one; if you already use Turbo, you don't need this package.
  • Analytics double-counting. Swup doesn't fire a page view automatically. Send one from page:view (not page:load), and remove the hard-coded inline GA snippet from base.html.twig or you'll see the initial page hit twice.
  • Debugging. In dev, pass options: { logLevel: 'debug' } to the component, and inspect window.swup in the console. The Swup hooks (window.swup.hooks.on('visit:start', console.log)) are the fastest way to see whether a navigation is even being intercepted.
  • Cache and preload. @swup/preload-plugin is a huge perceived-performance win but preloads on link:hover/touchstart — make sure your controllers are idempotent and cheap for GETs, and exclude any heavy report/export links with data-no-swup.
  • Accessibility / focus. After a swap, focus stays on the old node and screen readers don't announce the new page. Add @swup/accessibility-plugin or manually move focus to <main> on page:load. This is easy to forget and hard to catch in a demo.
  • Version drift. This is a small package (33 ★), older than the flagship UX components — the docs on Swup.js.org are your real reference, not the Symfony UX page. Pin swup in importmap.php and read the Swup changelog before upgrading; breaking changes in the JS library surface here first.
  • Webpack Encore users: the Flex recipe won't add swup to package.json. Install it yourself, or importmap:require swup and configure Encore to copy the @swup/* modules; otherwise the Stimulus controller boots but Swup is undefined.
  • Extension points to remember: the containers prop for multi-region pages, options.plugins for Swup plugins, and Swup hooks (visit:start, content:replace, page:load, page:view, visit:end) for anything app-specific. Between those three you can cover almost every customization without forking the bundle.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin