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

Layout Bundle Laravel Package

retitalia/layout-bundle

Retitalia LayoutBundle provides a flexible layout layer for PHP applications, with reusable templates, configurable slots, and rendering helpers that keep page structure consistent and views modular, maintainable, and easy to adapt across projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Acknowledge the framework mismatch up front. Despite the "Laravel developer" framing, retitalia/layout-bundle is a Symfony bundle targeting the AgID / Bootstrap Italia design system. It will not composer require into a Laravel app cleanly. Day-to-day, a Laravel developer uses this package one of two ways:

  1. As a reference/template source — you clone the repo, read the Twig templates in Resources/views/, and re-implement the markup in Blade. This is the realistic path for most teams.
  2. Inside an actual Symfony project — if the client engagement is Symfony-based, install it directly and extend the base layout as designed.

There is no Laravel service provider to publish, no Artisan command to run, no config/layout-bundle.php to copy. Plan accordingly.

Minimal steps on a Laravel project (the common case).

  1. Clone the repository locally; do not add it to composer.json.
    git clone https://github.com/RetItaliaSpA/LayoutBundle vendor-ref/layout-bundle
    
  2. Inventory what matters:
    • Resources/views/ — the base layout, header, footer, breadcrumb, cookie banner, skip links. This is the product.
    • Resources/public/ — CSS/JS assets, typically derived from Bootstrap Italia and AgID design tokens.
    • src/ — a Symfony Bundle class plus any Twig extensions (usually minimal). Skim it once; it rarely contains logic worth porting.
    • composer.json — tells you which Bootstrap Italia and Twig versions the upstream targeted. Pin your package.json to the same Bootstrap Italia release to keep the markup compatible.
  3. Copy the assets into your own pipeline:
    cp -r vendor-ref/layout-bundle/Resources/public/* resources/css/vendor/layout-bundle/
    
    Then re-export them through Vite or Mix so they're versioned with your app, not served from public/ as a symlinked bundle.
  4. Copy the Twig templates into resources/views/layouts/pa/ and start converting them one at a time — see Implementation Patterns below.

Where to look first (in order of signal).

  • Resources/views/base.html.twig — the top-level shell. Look at its blocks (head, body_start, content, footer, scripts) to understand where customization is expected.
  • Resources/views/partials/ (or whatever the folder is named) — header, footer, nav, breadcrumb. Each is usually self-contained.
  • Resources/public/ — the actual Bootstrap Italia bundle. Note the version. Note any AgID-specific overrides.
  • composer.json — Symfony and Twig version constraints tell you how old the templates are. Older Symfony versions mean older Twig conventions (e.g. {% spaceless %} blocks you'll need to handle differently in Blade).
  • LICENSE — critical. It is often not stated in the README and unclear licensing means you cannot ship it. Verify before porting.

First use case that proves value. Render a single AGID-shaped page — institutional header with government branding, primary nav, breadcrumb, main content, and footer with legal/accessibility links — inside a new Laravel app, wired up via Blade components and Vite. Do this before committing to a full port. It surfaces the biggest translation problems (Symfony asset(), path(), |trans, Twig macros) in an afternoon instead of a sprint.

Implementation Patterns

Pattern 1 — Component-per-region, not a monolithic layout. The upstream Twig layout typically defines a <header>, <nav>, <main>, <footer> as separate partials included from base.html.twig. Mirror that in Blade as anonymous components rather than one giant resources/views/layouts/pa.blade.php:

resources/views/layouts/pa.blade.php          @extends-friendly <html> shell
resources/views/components/pa/header.blade.php
resources/views/components/pa/nav.blade.php
resources/views/components/pa/breadcrumb.blade.php
resources/views/components/pa/footer.blade.php
resources/views/components/pa/skip-links.blade.php

This lets a page compose them:

<x-pa.layout :title="$title">
    <x-pa.header :org="$org" />
    <x-pa.nav :items="$nav" />
    <x-pa.breadcrumb :trail="$breadcrumb" />
    <main id="main">
        @yield('content')
    </main>
    <x-pa.footer :legal="$legal" />
</x-pa.layout>

Keeping them as components (not @include) gives you slot-based customization without editing the vendor templates on every upgrade.

Pattern 2 — Map Twig blocks to Blade slots, and Twig macros to components. Twig's {% block head %} maps cleanly to a @section('head') in an @extends layout, or a named slot ({{ $head ?? '' }}) in a component. Twig macros (used heavily for nav items, breadcrumb links, form fields) do not translate 1:1 — expand each macro into its own Blade component and pass arguments as attributes. If a macro has internal loops with is iterable checks, port the guard logic into a PHP @php block or a small view model.

Pattern 3 — One configuration file, loaded from a view composer. The upstream bundle has YAML configuration for branding, contact URLs, legal links, and nav structure. Reproduce that as config/pa.php:

return [
    'organization' => [
        'name' => env('PA_ORG_NAME', 'Comune di Esempio'),
        'url'  => env('PA_ORG_URL'),
        'logo' => env('PA_ORG_LOGO'),
    ],
    'header' => ['show_search' => true, 'show_login' => true],
    'nav' => [], // or loaded from DB
    'footer' => ['legal_links' => [...], 'accessibility_url' => env('PA_ACCESSIBILITY_URL')],
    'cookie_banner' => ['enabled' => true],
];

Expose it to layouts with a View::composer('layouts.pa', ...) so templates stay clean. This is the equivalent of the bundle's Configuration.php and it keeps the Blade files portable.

Pattern 4 — Rewrite Twig's Symfony helpers to Laravel equivalents at the call site. Every {{ asset('...') }} becomes @vite(...) or {{ Vite::asset(...) }}; every {{ path('route') }} becomes {{ route('name') }}; every {{ absolute_url(...) }} becomes url(...); every {{ 'label'|trans }} becomes {{ __('label') }}. Do this mechanically during the port and grep for leftovers — the Twig helpers silently pass through to Blade's {{ }} and produce broken strings if you leave them.

Pattern 5 — Route the assets through Vite (or Mix), not Symfony's asset installer. The upstream flow is php bin/console assets:install, which copies Resources/public/ into public/bundles/.... In Laravel you should instead:

  1. Copy Resources/public/ into resources/css/pa/ and resources/js/pa/.
  2. Import them from resources/css/app.css and resources/js/app.js.
  3. Reference them via @vite(['resources/css/app.css', 'resources/js/app.js']) in the layout head.

This gives you content hashing, cache busting, and version pinning for free — and avoids the classic bug where an upgraded template references assets from the old symlinked bundle.

Pattern 6 — Treat locale as Italian-first, English-optional. The AgID baseline assumes Italian copy; labels, breadcrumb separators, month names, and legal notices are all hardcoded Italian in the templates. Add resources/lang/it/pa.php and resources/lang/en/pa.php and pull every visible string from them during the port — this is not work you want to backfill after launch. If the client operates an English version of the PA site (@see: many comuni do), you will be asked.

Pattern 7 — Wire the cookie banner to your consent layer, not the bundle's. The upstream cookie markup is a shell — it typically has no logic. Hook it to whatever consent manager the project uses (a small Alpine component, a JS library, or Cookiebot). Do not port any inline <script> from the Twig templates; strip them during the copy and reimplement in a Vite-bundled ES module so you keep CSP compatibility.

Pattern 8 — Provide a "shell-only" page for pre-sales and QA. Build one route that renders just the layout with placeholder content — header, nav, breadcrumb, footer, empty <main>. Use it for accessibility scans (axe, Lighthouse), design review, and demo builds. It's the fastest way to prove the shell is compliant without the noise of page content, and it becomes your regression test page.

Gotchas and Tips

It is not a Laravel package. No service provider, no facade, no Artisan. composer require retitalia/layout-bundle in a Laravel app will pull Symfony's dependency tree and may conflict with your existing Symfony components. There is no supported adapter for Laravel (no TwigBridge path is documented, and introducing one is a month of work for a header and a footer). Port the templates.

The licence is not always stated. Before shipping anything derived from this repo, confirm the LICENSE file exists and permits your use. If it is missing or ambiguous, treat it as non-redistributable and have the client's legal team clear it — do not assume MIT.

Upstream is effectively unmaintained. No dependents, no stated release cadence, no maintainer. That means: pin the Bootstrap Italia version your port is based on, keep the upstream commit hash in a comment at the top of each Blade file, and own every accessibility fix yourself. Do not expect upstream to fix a WCAG defect you report.

Twig-only helpers fail silently in Blade. {{ asset('x') }} in Blade calls Laravel's global asset() helper — which works, but resolves to a path relative to your app root, so the output is wrong (not an error). Same for path() (undefined function → exception, better) and |trans (undefined filter → exception). Grep your ported Blade files for stray {{ asset(, {{ path(, |trans, {{ absolute_url( before considering the port complete.

Twig macros do not become Blade components automatically. Twig macros often accept default arguments and use {% if x is defined %} — Blade components require explicit @props declarations for every attribute. When porting a macro, list every attribute the macro touches in @props. Missing one produces an "undefined variable" error that only appears on certain pages.

Escaping is not automatically equivalent. Twig escapes HTML by default; Blade also escapes by default in {{ }} — but the upstream templates sometimes use {{ var|raw }} for internal markup (nav items with icons, breadcrumb separators). Every |raw you port becomes {!! ... !!}. Audit each one: if the escaped content is user-controllable, do not carry the raw over. A layout template is an XSS sink if used carelessly, and unmaintained frontend templates are a common source of exactly that.

The template is not an accessibility certificate. AgID/Bootstrap Italia markup gives you a strong starting point for landmarks, focus order, skip links, and language attributes — but WCAG 2.1 AA conformance is an audit outcome, not a template property. Budget an external accessibility review. If the project has a mandatory conformance clause in the tender, this is not optional, and pointing at the upstream repo as evidence of conformance is a procurement risk.

Cookie banner markup is inert. Expect an <div class="cookie-banner"> in the Twig partial with no JS behind it. Do not assume the "accept" button works. Wire it to your consent manager — and do not port inline <script> blocks from the upstream, because they almost certainly violate a strict CSP.

Static assets live in two places historically. If the bundle was installed via composer require, assets land under public/bundles/retitalialayout/. If you cloned the repo, they live under Resources/public/. These two paths produce different URLs. When you find yourself debugging a 404 for bootstrap-italia.min.css in a Blade page, check which one the template assumed — the answer is usually "the composer one, and you cloned."

Bootstrap Italia versions do not guarantee markup compatibility. The design system evolves: header/footer component classnames have changed between minor releases, and CSS custom property names drift. Pinning the Bootstrap Italia version in package.json is mandatory, but so is testing the layout against that pinned version — the upstream template may have been written against an older release than the one you install.

Breadcrumb data shape varies. Twig's breadcrumb macro typically expects an array of {label, url} objects (or a Symfony-specific route descriptor). Decide on a single shape — an array of ['label' => ..., 'url' => ...] — and enforce it in a view composer. Otherwise every page ends up constructing its own slightly different structure, and the nav breaks on the first page that omits a key.

Translation of hardcoded Italian is a full-port activity, not a cleanup step. There are usually hundreds of small strings ("Cerca", "Accedi", "Amministrazione trasparente", "Note legali", "Vai al contenuto", month abbreviations) across header, nav, footer, and skip links. Extract them to lang files while you port each component — retrofitting is a mechanical, error-prone, low-value task that always gets deferred.

Vite asset URLs are relative to your app root unless configured otherwise. If the PA site is served from a subpath (/comune/esempio/), set base in vite.config.js and confirm the layout's <link> and <script> tags honour it. The upstream bundle assumes root-served assets; this bites specifically on multi-tenant or path-prefixed deployments.

Skip links and the lang attribute need server-side data. The <html lang="it"> and the skip-link target (#main) are trivial but easy to hardcode incorrectly in Blade. Bind lang to app()->getLocale() and make sure the id="main" on the <main> element is not duplicated by any page that also declares one. Duplicate ids are a common accessibility regression introduced during the port.

Do not import the upstream's Bundle class "just in case". In a Laravel app there is nothing on the Symfony side you need. Leave src/ untouched; only Resources/views/ and Resources/public/ are useful to you.

Ownership must be named before adopting. The most likely failure mode is not a runtime crash — it's slow template drift: Bootstrap Italia moves, the ported Blade doesn't, accessibility regressions accumulate, and nobody notices until the next audit. If you cannot name an owner who will re-run accessibility scans on a cadence, hand-roll the shell instead. It is not significantly more work than the port, and you keep the upgrade path under your own control.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels