- t3bootstrap-site-package: composer + site sets + when to add a local extension
- t3bootstrap-overrides: 4-layer override surface (Settings → SCSS → Fluid → DataProcessor)
- t3bootstrap-content-elements: 23 t3bs_* CE catalog + migration matrix (HTML/WP/Joomla/older TYPO3) + Flux-bs5 upgrade wizard
Mirrors the structure of wapplersystems/typo3-skills (.claude-plugin/{plugin.json,marketplace.json}, .agents/skills/<name>/{SKILL.md,agents/openai.yaml}).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 KiB
name, description
| name | description |
|---|---|
| t3bootstrap-overrides | 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-packageexplains 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
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/<site>/settings.yaml automatically. Reviewable in git.
Better — customer extension's Site Set
Settings shipped from a customer extension's Configuration/Sets/<Customer>/settings.yaml:
# local_packages/<customer>/site_<customer>/Configuration/Sets/Site<Customer>/settings.yaml
plugin.tx_template.view.templateRootPath: 'EXT:site_<customer>/Resources/Private/Templates/'
plugin.tx_template.view.partialRootPath: 'EXT:site_<customer>/Resources/Private/Partials/'
plugin.tx_template.view.layoutRootPath: 'EXT:site_<customer>/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:
# local_packages/<customer>/site_<customer>/Configuration/Sets/Site<Customer>/constants.typoscript
plugin.tx_template.view.templateRootPath = EXT:site_<customer>/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:
- Defines overrides
- Imports the t3bootstrap base
- Imports Bootstrap 5
- Adds any extra component CSS
Master variables file
vendor/t3bootstrap/template/Resources/Public/SCSS/_variables.scss
Representative excerpt — all values are !default, so you can re-set them upstream:
$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
/* local_packages/<customer>/site_<customer>/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.
/* 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):
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/<customer>/site_<customer>/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 <html><body>…
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:
# In a customer extension's setup.typoscript
lib.contentElement.templateRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Templates/
lib.contentElement.partialRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Partials/
lib.contentElement.layoutRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Layouts/
Upstream-rendered CTypes you can override there:
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:
# Override e.g. the Card.html template for t3bs_card
lib.t3bsContent.templateRootPaths.200 = EXT:site_<customer>/Resources/Private/T3bsContent/Templates/
lib.t3bsContent.partialRootPaths.200 = EXT:site_<customer>/Resources/Private/T3bsContent/Partials/
Source:
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\FrameClassesProcessorwith 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:
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
# Customer Site Set setup.typoscript
lib.t3bsContent.dataProcessing.950 = Vendor\SiteCustomer\DataProcessing\ReadTimeProcessor
lib.t3bsContent.dataProcessing.950.target = readTime
Replacing an upstream processor
# 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/<customer>/site_<customer>/
├── composer.json
├── ext_emconf.php
├── ext_localconf.php
├── Classes/
│ ├── DataProcessing/ # Layer 4
│ └── ViewHelpers/
├── Configuration/
│ ├── Sets/
│ │ └── Site<Customer>/
│ │ ├── 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<Customer>/config.yaml:
name: <customer>/site-<customer>
label: <Customer> Site Customization
dependencies:
- t3bootstrap/template
- t3bootstrap/container-bs5-templates
setup.typoscript:
# Page templates
plugin.tx_template {
view {
templateRootPath = EXT:site_<customer>/Resources/Private/Templates/
partialRootPath = EXT:site_<customer>/Resources/Private/Partials/
layoutRootPath = EXT:site_<customer>/Resources/Private/Layouts/
}
}
# fluid_styled_content extensions
lib.contentElement {
templateRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Templates/
partialRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Partials/
layoutRootPaths.20 = EXT:site_<customer>/Resources/Private/ContentElements/Layouts/
}
# t3bs_* CEs
lib.t3bsContent {
templateRootPaths.200 = EXT:site_<customer>/Resources/Private/T3bsContent/Templates/
partialRootPaths.200 = EXT:site_<customer>/Resources/Private/T3bsContent/Partials/
}
# Custom DataProcessor
lib.t3bsContent.dataProcessing.950 = Vendor\SiteCustomer\DataProcessing\ReadTimeProcessor
Then add the customer set to the site config:
# config/sites/<site-id>/config.yaml
dependencies:
- t3bootstrap/template
- t3bootstrap/container-bs5-templates
- <customer>/site-<customer> # <- 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 <style> in PageTSconfig to "patch" colors |
Fragile, gets purged by cache flushes, not visible at build time | Layer 2 (SCSS !default) or CSS custom properties |
| Copying ALL upstream partials into the customer ext "just in case" | Now you own every change forever | Copy only what you override |
Setting templateRootPaths.0 in the customer extension |
Shadows the original; future upstream changes invisible | Use index 10 (or higher), keep 0 for vendor |
| Cloning the whole vendor SCSS tree | Re-doing Bootstrap themability | Re-define !default variables in customer SCSS, import vendor base |
Hardcoding EXT:template/... paths in the customer ext templates |
The customer extension shouldn't refer to upstream paths | Use Fluid layout/partial inheritance — <f:layout name="Default"/> finds it via layoutRootPaths automatically |
Modifying lib.dynamicContent "to add fields" |
The DataProcessor pipeline is the right hook | Add a processor at index 900+ |
Verification Checklist
After any override change:
ddev exec vendor/bin/typo3 cache:flush— TypoScript and Fluid template paths are cached.- Reload the frontend; if SCSS changed, run the customer's CSS build (
npm run build, orws-scsswatcher). - Inspect the rendered HTML — confirm your override is the one rendered (look for a distinctive marker comment you added).
- TYPO3 backend → Maintenance → "Render TypoScript" → confirm the right
templateRootPaths.10/.20/.200are populated with your paths. - If layered overrides don't seem to take effect: list
lib.t3bsContent.templateRootPathsviavendor/bin/typo3 ts:setup --path=lib.t3bsContent.templateRootPaths(fromclaude-diagnostics) — index values shown in numeric order.
Reference Files in This Project
vendor/t3bootstrap/template/Configuration/TypoScript/constants.typoscript # all constants
vendor/t3bootstrap/template/Configuration/TypoScript/HTML/Page/body.typoscript # main layoutRootPaths block
vendor/t3bootstrap/template/Resources/Public/SCSS/_variables.scss # SCSS variables
vendor/t3bootstrap/template/Resources/Private/Partials/ # partials to override
vendor/t3bootstrap/template/Resources/Private/Templates/Page/ # page templates
vendor/t3bootstrap/core/Resources/Private/Extensions/fluid_styled_content/ # core CE overrides at index 5/9
vendor/t3bootstrap/container-bs5-templates/Configuration/Sets/ContainerBs5Templates/setup.typoscript # lib.t3bsContent declaration
vendor/t3bootstrap/container-bs5-templates/Resources/Private/Templates/ # t3bs_* CE templates
For CE-by-CE migration mapping and the t3bs_* catalog, see the
t3bootstrap-content-elements skill.