--- name: t3bootstrap-overrides description: Customize a T3Bootstrap-based TYPO3 v14 project minimally — without forking the upstream `t3bootstrap/*` packages. Covers the four override surfaces in order of invasiveness (Site Settings / TypoScript constants → SCSS `!default` variables → Fluid `templateRootPaths.10` overrides → custom DataProcessors), how to decide which surface fits a given change, the path-index conventions (`0` core, `5` core extensions, `10` site-package, `100` container-bs5, `200` Frameless), and the standard layout for a customer-specific extension that ships such overrides. Use this skill whenever the customer wants different colors, fonts, page structure, navigation, footer, content-element rendering, or any other deviation from upstream T3Bootstrap defaults. --- # T3Bootstrap Overrides Use this skill when customizing a TYPO3 v14 project on the T3Bootstrap stack. The goal: **touch nothing in `vendor/`**, ship the customization as a thin customer extension, keep `composer update` upgrade-safe. > **Companion skill:** [`t3bootstrap-site-package`](../t3bootstrap-site-package/SKILL.md) explains the underlying setup. Start there if the project isn't bootstrapped yet. ## Override Surfaces — Pick the Least Invasive T3Bootstrap exposes **four layers** of customization. Always start with the highest layer that can solve the problem; descend only when forced. ``` 1. Site Settings / TypoScript constants ← most upgrade-safe, no code 2. SCSS !default variable overrides ← brand colors, fonts, spacing tokens 3. Fluid template overrides via index 10 ← structural HTML changes 4. Custom DataProcessor / PHP ← only when data shape must change ``` ### Decision table | Need | Layer | Example | | --- | --- | --- | | Change brand color | 2 (SCSS) or 1 (CSS custom property) | Re-define `$primary` | | Change header height / sticky behavior | 1 (constants) | `header.sticky` setting | | Show/hide language menu | 1 (constants) | `navigation.languageMenu` | | Different button styling | 1 + 2 | CSS custom property, or `$btn-padding-y` SCSS | | Add an extra CSS class to all CEs | 3 (override Content layout) | `Resources/Private/Layouts/Content/Default.html` | | Replace the Header partial with something completely different | 3 (override partial) | `Resources/Private/Partials/PageHeader.html` | | Change the data passed to a CE | 4 (custom DataProcessor) | Replace `FrameClassesProcessor` | If a question is at the boundary (e.g. "is this an SCSS change or a Fluid override?"), the answer is usually: **whichever is closer to the top of the table**. ## Layer 1 — Site Settings / TypoScript Constants The `t3bootstrap/template` set exposes a large set of constants under `Configuration/TypoScript/constants.typoscript`. Categories include navigation, language, text, font, search box, logo, banner, body tag, breadcrumb, color, color-mode, language defaults, screen, domains. ### Where they're defined ```text vendor/t3bootstrap/template/Configuration/TypoScript/constants.typoscript # the master file (~639 lines) vendor/t3bootstrap/template/Configuration/TypoScript/Plugin/Constants/*.typoscript # per-plugin (calendarize, indexedsearch, …) ``` ### How to override #### Best — Site Setting in the TYPO3 v14 Backend Site Settings module → "T3Bootstrap Template" category → change the value. Persisted to `config/sites//settings.yaml` automatically. Reviewable in git. #### Better — customer extension's Site Set Settings shipped from a customer extension's `Configuration/Sets//settings.yaml`: ```yaml # local_packages//site_/Configuration/Sets/Site/settings.yaml plugin.tx_template.view.templateRootPath: 'EXT:site_/Resources/Private/Templates/' plugin.tx_template.view.partialRootPath: 'EXT:site_/Resources/Private/Partials/' plugin.tx_template.view.layoutRootPath: 'EXT:site_/Resources/Private/Layouts/' # Color tokens at the constants level (CSS custom property variables will see them) color.primary: '#005262' color.secondary: '#55aa63' ``` #### Acceptable — TypoScript constants in a customer extension Only if you need conditions or includes that the Settings YAML can't express: ```typoscript # local_packages//site_/Configuration/Sets/Site/constants.typoscript plugin.tx_template.view.templateRootPath = EXT:site_/Resources/Private/Templates/ ``` > **Never** edit the upstream constants file. Any constant you want to change has a > matching site setting key — find it in the backend Constants Editor or in the master > constants file's `# cat=…` comments. ## Layer 2 — SCSS Variable Overrides T3Bootstrap's SCSS is built around Bootstrap 5 and uses `!default` on every variable so customer SCSS loaded **before** the vendor file wins. ### The compilation pipeline The stack relies on `wapplersystems/ws-scss` (or any project-set-up Node/Vite/PostCSS pipeline). The conventional entry point is one customer SCSS file that: 1. Defines overrides 2. Imports the t3bootstrap base 3. Imports Bootstrap 5 4. Adds any extra component CSS ### Master variables file ```text vendor/t3bootstrap/template/Resources/Public/SCSS/_variables.scss ``` Representative excerpt — **all values are `!default`, so you can re-set them upstream**: ```scss $primary: #005262 !default; $secondary: #55aa63 !default; $lightblue: #87c8e6 !default; $prefix: bs- !default; $custom-colors: ( dark-gray: #005262, gray: #999, graylight: #f7f7f7 ) !default; $icon-sizes: ( sm: 2rem, md: 3rem, lg: 4rem, xl: 6rem ) !default; $font-family-sans-serif: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif !default; $h1-font-size: 2.0rem !default; $h2-font-size: 1.8rem !default; $font-size-base: 0.95rem !default; ``` Related files in the same directory: `_colors.scss`, `_mixins.scss`, `_mmenu.scss`, `_variables-dark.scss`, `_variables-contrast-high.scss`, and the subdirectories `additions/`, `aos/`, `aria/`, `base-layout/`, `bootstrap/`, `components/`, `effects/`, `elements/`, `extensions/`, `fonts/`, `mixins/`. ### Customer override pattern ```scss /* local_packages//site_/Resources/Public/Scss/main.scss */ /* 1. Re-define variables BEFORE importing t3bootstrap */ $primary: #c81d25; $secondary: #2b6cb0; $font-family-sans-serif: 'Inter', sans-serif; $custom-colors: ( brand-dark: #1a1a2e, brand-light: #f5f5f5, ); /* Bootstrap defaults can also be redefined here, BEFORE bootstrap loads */ $btn-border-radius: 0; /* 2. Import the t3bootstrap base — this brings in Bootstrap 5 and all defaults */ @import 'EXT:template/Resources/Public/SCSS/base-layout'; /* 3. Project-specific additions */ .c-customer-banner { background: $primary; /* … */ } ``` `@import 'EXT:template/...'` works inside `ws_scss` — it resolves `EXT:` paths via the TYPO3 path resolver. If your toolchain doesn't, use a relative or absolute path. ### When to use CSS custom properties instead Bootstrap 5 ships **CSS custom properties** (`--bs-primary`, `--bs-body-color`, …) for runtime themability. T3Bootstrap respects them. ```css /* As an inline-style block on the page, or in a separate stylesheet loaded after Bootstrap */ :root { --bs-primary: #c81d25; --bs-secondary: #2b6cb0; } ``` **Rule of thumb:** values that must be available at runtime (dark mode, theme switching, editor-tweaked colors via Site Settings) → CSS custom properties. Values fixed at build time (font stack, spacing scale, breakpoints) → SCSS variables. ## Layer 3 — Fluid Template Overrides via Path Index Every Fluid-rendering object in T3Bootstrap exposes `templateRootPaths`, `partialRootPaths`, and `layoutRootPaths` as numerically-indexed arrays. **Higher index wins.** The convention is: | Index | Owner | Purpose | | --- | --- | --- | | `0` | t3bootstrap base | Original templates. Never touch. | | `5` | `t3bootstrap/core` extension overrides | E.g. core's `fluid_styled_content` overrides. | | `9` / `40` | `t3bootstrap/template` plugin-specific overrides | Felogin, fluid_styled_content, indexed_search etc. | | `10` | **Customer site package** | This is the slot you write to. | | `100` | `t3bootstrap/container-bs5-templates` | The `t3bs_*` CE templates. | | `200` | Frameless variant from `container-bs5-templates` | `Frameless/` layout dir for the "no-frame" CE option. | ### The page template's override hook `vendor/t3bootstrap/template/Configuration/TypoScript/HTML/Page/body.typoscript` (lines 77–90): ```typoscript layoutRootPaths { 0 = EXT:template/Resources/Private/Layouts/ 10 = {$plugin.tx_template.view.layoutRootPath} } templateRootPaths { 0 = EXT:template/Resources/Private/Templates/Page/ 10 = {$plugin.tx_template.view.templateRootPath} } partialRootPaths { 0 = EXT:template/Resources/Private/Partials/ 10 = {$plugin.tx_template.view.partialRootPath} } ``` Configure `plugin.tx_template.view.*` via Site Settings (preferred) or constants — see Layer 1. ### What's in `Resources/Private/Templates/Page/` upstream ``` 1Column.html 2Columns.html 2Columns2.html 3Columns.html Onepager.html ``` These are picked by the page's **backend layout**. Override one by copying it to `local_packages//site_/Resources/Private/Templates/2Columns.html` and modifying. ### Layouts upstream ``` Resources/Private/Layouts/ ├── Content.html # The wrapper used by all fluid_styled_content CEs └── Page.html # The HTML shell … ``` ### Partials upstream — what's worth overriding ``` Resources/Private/Partials/ ├── BackgroundMedia.html ├── Footer/ ← whole footer block partials (columns, copyright bar) ├── Header/ ← header column partials ├── Navigation/ ← main nav, breadcrumb, language menu, mobile burger ├── Hero.html ├── Navigation.html ├── PageFooter.html ├── PageHeader.html ├── Searchbox.html └── Skiplinks.html ``` #### Common override targets | Want to change | Override | | --- | --- | | Site-wide header structure | `Resources/Private/Partials/PageHeader.html` | | Logo placement, sticky behavior | `Resources/Private/Partials/Header/*.html` | | Navigation rendering | `Resources/Private/Partials/Navigation.html` + `Navigation/*.html` | | Search box markup | `Resources/Private/Partials/Searchbox.html` | | Footer layout | `Resources/Private/Partials/PageFooter.html` + `Footer/*.html` | | Skiplinks for a11y | `Resources/Private/Partials/Skiplinks.html` | | Hero markup | `Resources/Private/Partials/Hero.html` | | Background-media wrapper | `Resources/Private/Partials/BackgroundMedia.html` | > Copy the **whole** partial, then modify. Fluid does not "merge" — once index 10 > shadows index 0, it must contain the full template. ## Overriding `fluid_styled_content` CEs The stack already overrides core's `fluid_styled_content` at index `9` (template) and `5` (layouts) via `t3bootstrap/core`. To override **further**, point to a higher index: ```typoscript # In a customer extension's setup.typoscript lib.contentElement.templateRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Templates/ lib.contentElement.partialRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Partials/ lib.contentElement.layoutRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Layouts/ ``` Upstream-rendered CTypes you can override there: ```text vendor/t3bootstrap/core/Resources/Private/Extensions/fluid_styled_content/Templates/ ├── Textmedia.html ├── Textpic.html ├── Image.html ├── Table.html ├── Shortcut.html ├── Card.html ├── CardsCarousel.html ├── Counterbar.html ├── Countdown.html ├── CompareSlider.html └── NumberCarousel.html ``` For the `t3bs_*` container-based CEs use the **container_bs5_templates** path indices: ```typoscript # Override e.g. the Card.html template for t3bs_card lib.t3bsContent.templateRootPaths.200 = EXT:site_/Resources/Private/T3bsContent/Templates/ lib.t3bsContent.partialRootPaths.200 = EXT:site_/Resources/Private/T3bsContent/Partials/ ``` Source: ```text vendor/t3bootstrap/container-bs5-templates/Resources/Private/Templates/ ├── Card.html ← t3bs_card ├── Cards.html ← t3bs_cards ├── Accordion.html ← t3bs_accordion ├── AccordionItem.html ├── Tabs.html ├── TabItem.html ├── Carousel.html ├── CarouselItem.html ├── Container.html ├── FluidRow.html ├── Column.html ├── Alert.html ├── Panel.html ├── Well.html ├── Thumbnail.html ├── Media.html ├── Example.html ├── Timeline.html ├── TimelineItem.html ├── Buttongroup.html ├── Buttonlink.html ├── Megamenu.html └── MegamenuItem.html ``` ## Layer 4 — Custom DataProcessors Only needed when you want to **change the data shape** the Fluid templates see, not just the markup. Examples: - Add a derived field (e.g. a "read time" estimate for news items). - Replace `T3Bootstrap\Core\Frontend\DataProcessing\FrameClassesProcessor` with a custom one that emits different frame CSS classes. - Add a side-effect (logging, prefetching, conditional caching of related data). Upstream processors used by `lib.t3bsContent`: ```text T3Bootstrap\Core\Frontend\DataProcessing\IconProcessor # 706 T3Bootstrap\Core\Frontend\DataProcessing\FrameClassesProcessor # 707 T3Bootstrap\Core\Frontend\DataProcessing\AosDataAttributeProcessor # 708 T3Bootstrap\Core\Frontend\DataProcessing\ProcessedDataToRegisterProcessor # 900 ``` Plus core's `TYPO3\CMS\Frontend\DataProcessing\FilesProcessor` at 703 for `background_media`. ### Adding a custom processor ```typoscript # Customer Site Set setup.typoscript lib.t3bsContent.dataProcessing.950 = Vendor\SiteCustomer\DataProcessing\ReadTimeProcessor lib.t3bsContent.dataProcessing.950.target = readTime ``` ### Replacing an upstream processor ```typoscript # Replace at the same index — TypoScript merges shallow, so reassign: lib.t3bsContent.dataProcessing.707 = Vendor\SiteCustomer\DataProcessing\CustomFrameClassesProcessor lib.t3bsContent.dataProcessing.707 > lib.t3bsContent.dataProcessing.707 = Vendor\SiteCustomer\DataProcessing\CustomFrameClassesProcessor ``` The `>` operator clears the original config sub-tree first to prevent stray options. ### TSFE-aware processors On TYPO3 v14 there is no more `$GLOBALS['TSFE']`. T3Bootstrap's processors use the **`RegisterUtility`** static registry at `T3Bootstrap\Core\Utility\RegisterUtility` to share per-request state across processors and ViewHelpers. If your processor needs to publish a value for a later ViewHelper, write to that registry — don't reach for the removed TSFE register. ## Standard Customer-Extension Layout ``` local_packages//site_/ ├── composer.json ├── ext_emconf.php ├── ext_localconf.php ├── Classes/ │ ├── DataProcessing/ # Layer 4 │ └── ViewHelpers/ ├── Configuration/ │ ├── Sets/ │ │ └── Site/ │ │ ├── config.yaml # dependencies │ │ ├── settings.yaml # Layer 1 │ │ ├── constants.typoscript # Layer 1 (only if Settings YAML insufficient) │ │ └── setup.typoscript # Layer 3 path-index registration + Layer 4 processors │ └── TCA/Overrides/ └── Resources/ ├── Private/ │ ├── Templates/ # Page templates (Layer 3) │ ├── Layouts/ # Page + content layouts │ ├── Partials/ # Header/Footer/Nav/Hero/… (Layer 3) │ ├── ContentElements/ # fluid_styled_content overrides │ │ ├── Templates/ │ │ ├── Layouts/ │ │ └── Partials/ │ ├── T3bsContent/ # t3bs_* CE overrides │ │ ├── Templates/ │ │ └── Partials/ │ └── Language/ └── Public/ ├── Scss/ │ ├── main.scss # Layer 2 — !default overrides + imports │ └── _customer-variables.scss ├── Css/ # compiled output └── JavaScript/ ``` `Configuration/Sets/Site/config.yaml`: ```yaml name: /site- label: Site Customization dependencies: - t3bootstrap/template - t3bootstrap/container-bs5-templates ``` `setup.typoscript`: ```typoscript # Page templates plugin.tx_template { view { templateRootPath = EXT:site_/Resources/Private/Templates/ partialRootPath = EXT:site_/Resources/Private/Partials/ layoutRootPath = EXT:site_/Resources/Private/Layouts/ } } # fluid_styled_content extensions lib.contentElement { templateRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Templates/ partialRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Partials/ layoutRootPaths.20 = EXT:site_/Resources/Private/ContentElements/Layouts/ } # t3bs_* CEs lib.t3bsContent { templateRootPaths.200 = EXT:site_/Resources/Private/T3bsContent/Templates/ partialRootPaths.200 = EXT:site_/Resources/Private/T3bsContent/Partials/ } # Custom DataProcessor lib.t3bsContent.dataProcessing.950 = Vendor\SiteCustomer\DataProcessing\ReadTimeProcessor ``` Then add the customer set to the site config: ```yaml # config/sites//config.yaml dependencies: - t3bootstrap/template - t3bootstrap/container-bs5-templates - /site- # <- new ``` ## Anti-Patterns | Anti-pattern | Why it's bad | Correct approach | | --- | --- | --- | | Editing files under `vendor/t3bootstrap/*/` | Composer wipes changes on next update | Override via the customer extension's path index `10` (or higher) | | Forking `t3bootstrap/template` in customer's GitHub | Loses upstream fixes; merge conflicts forever | Customer extension + targeted partial overrides | | Adding raw `