Skip to content

Add changelog / requirement / install .. for every package - #1

Open
smnandre wants to merge 1 commit into
2.xfrom
sa/changelog-req
Open

Add changelog / requirement / install .. for every package#1
smnandre wants to merge 1 commit into
2.xfrom
sa/changelog-req

Conversation

@smnandre

Copy link
Copy Markdown
Owner
Capture d’écran 2024-05-23 à 01 58 41

(not designed for now)

Capture d’écran 2024-05-23 à 01 58 35

@Kocal
Kocal force-pushed the sa/changelog-req branch from 19551fd to 50925b7 Compare August 15, 2024 10:28
@Kocal
Kocal force-pushed the sa/changelog-req branch from 50925b7 to 9bd4ca5 Compare August 15, 2024 10:28
smnandre pushed a commit that referenced this pull request Aug 20, 2025
This PR was merged into the 2.x branch.

Discussion
----------

[CI] Fix highest dev jobs

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | no <!-- required for new features -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Update/add documentation as required (we can help!)
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

Temporary workaround for fixing highest jobs:
```
+ cd src/Autocomplete
+ composer config minimum-stability dev --ansi
+ composer update --no-progress --no-interaction --ansi
Loading composer repositories with package information
Updating dependencies
Lock file operations: 76 installs, 0 updates, 0 removals
  - Locking behat/mink (dev-master 916471d)
  - Locking doctrine/collections (2.4.x-dev 0cc9698)
  - Locking doctrine/dbal (4.4.x-dev 57c983f)
  - Locking doctrine/deprecations (1.1.x-dev fad1854)
  - Locking doctrine/doctrine-bundle (2.16.x-dev 820d574)
  - Locking doctrine/event-manager (2.1.x-dev 5e5da55)
  - Locking doctrine/inflector (2.2.x-dev 6d6c962)
  - Locking doctrine/instantiator (2.0.x-dev 0daa7cf)
  - Locking doctrine/lexer (3.1.x-dev 042e47e)
  - Locking doctrine/orm (3.6.x-dev c1f7a60)
  - Locking doctrine/persistence (4.1.x-dev c392605)
  - Locking doctrine/sql-formatter (1.5.x-dev c894b54)
  - Locking fakerphp/faker (1.24.x-dev f7ac507)
  - Locking masterminds/html5 (2.10.0)
  - Locking mtdowling/jmespath.php (dev-master a2a865e)
  - Locking nikic/php-parser (dev-master f103601)
  - Locking psr/cache (dev-master 588d5ff)
  - Locking psr/clock (1.0.0)
  - Locking psr/container (dev-master 7079847)
  - Locking psr/event-dispatcher (dev-master bbd9eac)
  - Locking psr/log (dev-master f16e1d5)
  - Locking symfony/browser-kit (7.4.x-dev 7f5f0fb)
  - Locking symfony/cache (7.4.x-dev 1b95178)
  - Locking symfony/cache-contracts (dev-main 5d68a57)
  - Locking symfony/clock (8.0.x-dev be52243)
  - Locking symfony/config (7.4.x-dev cd72a1e)
  - Locking symfony/console (7.4.x-dev 0fe6c7f)
  - Locking symfony/css-selector (7.4.x-dev 57500fe)
  - Locking symfony/dependency-injection (7.4.x-dev c1ff387)
  - Locking symfony/deprecation-contracts (dev-main 63afe74)
  - Locking symfony/doctrine-bridge (7.4.x-dev fef1186)
  - Locking symfony/dom-crawler (7.4.x-dev 7b9b135)
  - Locking symfony/error-handler (8.0.x-dev 7540d8d)
  - Locking symfony/event-dispatcher (8.0.x-dev f28a414)
  - Locking symfony/event-dispatcher-contracts (dev-main 59eb412)
  - Locking symfony/filesystem (7.4.x-dev 633c6fd)
  - Locking symfony/finder (7.4.x-dev 57861c3)
  - Locking symfony/form (8.0.x-dev 5320af1)
  - Locking symfony/framework-bundle (7.4.x-dev 11a5ef8)
  - Locking symfony/http-foundation (8.0.x-dev 4f8f710)
  - Locking symfony/http-kernel (7.4.x-dev 2d616d7)
  - Locking symfony/maker-bundle (1.x-dev c86da84)
  - Locking symfony/options-resolver (8.0.x-dev 17b3cb5)
  - Locking symfony/password-hasher (8.0.x-dev 80e7c7e)
  - Locking symfony/phpunit-bridge (8.0.x-dev b77163b)
  - Locking symfony/polyfill-ctype (1.x-dev a3cc8b0)
  - Locking symfony/polyfill-intl-grapheme (1.x-dev 3808721)
  - Locking symfony/polyfill-intl-icu (1.x-dev bfc8fa1)
  - Locking symfony/polyfill-intl-normalizer (1.x-dev 3833d72)
  - Locking symfony/polyfill-mbstring (1.x-dev 6d857f4)
  - Locking symfony/polyfill-php81 (1.x-dev 4a4cfc2)
  - Locking symfony/polyfill-php84 (1.x-dev d8ced4d)
  - Locking symfony/polyfill-php85 (1.x-dev d4e5fcd)
  - Locking symfony/polyfill-uuid (1.x-dev 21533be)
  - Locking symfony/process (7.4.x-dev 410c833)
  - Locking symfony/property-access (8.0.x-dev 74df691)
  - Locking symfony/property-info (8.0.x-dev bde1637)
  - Locking symfony/routing (8.0.x-dev 8d7ffad)
  - Locking symfony/security-bundle (8.0.x-dev db7907a)
  - Locking symfony/security-core (8.0.x-dev 7cc6cd2)
  - Locking symfony/security-csrf (8.0.x-dev 2790fe6)
  - Locking symfony/security-http (8.0.x-dev f99eb69)
  - Locking symfony/service-contracts (dev-main 4511256)
  - Locking symfony/string (8.0.x-dev d7b049d)
  - Locking symfony/translation-contracts (dev-main 65a8bc8)
  - Locking symfony/twig-bridge (8.0.x-dev b068de9)
  - Locking symfony/twig-bundle (8.0.x-dev 5f79218)
  - Locking symfony/type-info (8.0.x-dev cbab7d4)
  - Locking symfony/uid (8.0.x-dev 600055f)
  - Locking symfony/var-dumper (8.0.x-dev f8902df)
  - Locking symfony/var-exporter (7.4.x-dev 2c309a3)
  - Locking twig/twig (3.x-dev b1d2a9c)
  - Locking zenstruck/assert (1.x-dev b9a37a5)
  - Locking zenstruck/browser (1.x-dev 615bed9)
  - Locking zenstruck/callback (1.x-dev 42be837)
  - Locking zenstruck/foundry (2.x-dev 0a65872)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 76 installs, 0 updates, 0 removals
  - Downloading symfony/http-kernel (7.4.x-dev 2d616d7)
  - Downloading symfony/finder (7.4.x-dev 57861c3)
  - Downloading symfony/filesystem (7.4.x-dev 633c6fd)
  - Downloading symfony/var-exporter (7.4.x-dev 2c309a3)
  - Downloading symfony/dependency-injection (7.4.x-dev c1ff387)
  - Downloading symfony/config (7.4.x-dev cd72a1e)
  - Downloading symfony/cache (7.4.x-dev 1b95178)
  - Downloading symfony/framework-bundle (7.4.x-dev 11a5ef8)
  - Downloading doctrine/event-manager (2.1.x-dev 5e5da55)
  - Downloading doctrine/persistence (4.1.x-dev c392605)
  - Downloading symfony/doctrine-bridge (7.4.x-dev fef1186)
  - Downloading doctrine/sql-formatter (1.5.x-dev c894b54)
  - Downloading doctrine/deprecations (1.1.x-dev fad1854)
  - Downloading doctrine/dbal (4.4.x-dev 57c983f)
  - Downloading doctrine/doctrine-bundle (2.16.x-dev 820d574)
  - Downloading doctrine/lexer (3.1.x-dev 042e47e)
  - Downloading doctrine/instantiator (2.0.x-dev 0daa7cf)
  - Downloading doctrine/inflector (2.2.x-dev 6d6c962)
  - Downloading symfony/polyfill-php84 (1.x-dev d8ced4d)
  - Downloading doctrine/collections (2.4.x-dev 0cc9698)
  - Downloading doctrine/orm (3.6.x-dev c1f7a60)
  - Downloading masterminds/html5 (2.10.0)
  - Downloading mtdowling/jmespath.php (dev-master a2a865e)
  - Downloading symfony/process (7.4.x-dev 410c833)
  - Downloading nikic/php-parser (dev-master f103601)
  - Downloading symfony/maker-bundle (1.x-dev c86da84)
  - Downloading symfony/password-hasher (8.0.x-dev 80e7c7e)
  - Downloading symfony/security-core (8.0.x-dev 7cc6cd2)
  - Downloading symfony/security-http (8.0.x-dev f99eb69)
  - Downloading symfony/security-csrf (8.0.x-dev 2790fe6)
  - Downloading symfony/clock (8.0.x-dev be52243)
  - Downloading symfony/security-bundle (8.0.x-dev db7907a)
  - Downloading symfony/polyfill-uuid (1.x-dev 21533be)
  - Downloading symfony/uid (8.0.x-dev 600055f)
  - Downloading zenstruck/callback (1.x-dev 42be837)
  - Downloading zenstruck/assert (1.x-dev b9a37a5)
  - Downloading symfony/dom-crawler (7.4.x-dev 7b9b135)
  - Downloading symfony/css-selector (7.4.x-dev 57500fe)
  - Downloading symfony/browser-kit (7.4.x-dev 7f5f0fb)
  - Downloading behat/mink (dev-master 916471d)
  - Downloading zenstruck/browser (1.x-dev 615bed9)
  - Downloading fakerphp/faker (1.24.x-dev f7ac507)
  - Downloading zenstruck/foundry (2.x-dev 0a65872)
  - Installing symfony/deprecation-contracts (dev-main 63afe74): Extracting archive
  - Installing psr/container (dev-master 7079847): Extracting archive
  - Installing symfony/service-contracts (dev-main 4511256): Extracting archive
  - Installing symfony/routing (8.0.x-dev 8d7ffad): Extracting archive
  - Installing symfony/polyfill-php85 (1.x-dev d4e5fcd): Extracting archive
  - Installing symfony/polyfill-mbstring (1.x-dev 6d857f4): Extracting archive
  - Installing symfony/polyfill-ctype (1.x-dev a3cc8b0): Extracting archive
  - Installing symfony/http-foundation (8.0.x-dev 4f8f710): Extracting archive
  - Installing psr/event-dispatcher (dev-master bbd9eac): Extracting archive
  - Installing symfony/event-dispatcher-contracts (dev-main 59eb412): Extracting archive
  - Installing symfony/event-dispatcher (8.0.x-dev f28a414): Extracting archive
  - Installing symfony/var-dumper (8.0.x-dev f8902df): Extracting archive
  - Installing psr/log (dev-master f16e1d5): Extracting archive
  - Installing symfony/error-handler (8.0.x-dev 7540d8d): Extracting archive
  - Installing symfony/http-kernel (7.4.x-dev 2d616d7): Extracting archive
  - Installing symfony/finder (7.4.x-dev 57861c3): Extracting archive
  - Installing symfony/filesystem (7.4.x-dev 633c6fd): Extracting archive
  - Installing symfony/var-exporter (7.4.x-dev 2c309a3): Extracting archive
  - Installing symfony/dependency-injection (7.4.x-dev c1ff387): Extracting archive
  - Installing symfony/config (7.4.x-dev cd72a1e): Extracting archive
  - Installing psr/cache (dev-master 588d5ff): Extracting archive
  - Installing symfony/cache-contracts (dev-main 5d68a57): Extracting archive
  - Installing symfony/cache (7.4.x-dev 1b95178): Extracting archive
  - Installing symfony/framework-bundle (7.4.x-dev 11a5ef8): Extracting archive
  - Installing doctrine/event-manager (2.1.x-dev 5e5da55): Extracting archive
  - Installing doctrine/persistence (4.1.x-dev c392605): Extracting archive
  - Installing symfony/doctrine-bridge (7.4.x-dev fef1186): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (1.x-dev 3833d72): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (1.x-dev 3808721): Extracting archive
  - Installing symfony/string (8.0.x-dev d7b049d): Extracting archive
  - Installing symfony/console (7.4.x-dev 0fe6c7f): Extracting archive
  - Installing doctrine/sql-formatter (1.5.x-dev c894b54): Extracting archive
  - Installing doctrine/deprecations (1.1.x-dev fad1854): Extracting archive
  - Installing doctrine/dbal (4.4.x-dev 57c983f): Extracting archive
  - Installing doctrine/doctrine-bundle (2.16.x-dev 820d574): Extracting archive
  - Installing doctrine/lexer (3.1.x-dev 042e47e): Extracting archive
  - Installing doctrine/instantiator (2.0.x-dev 0daa7cf): Extracting archive
  - Installing doctrine/inflector (2.2.x-dev 6d6c962): Extracting archive
  - Installing symfony/polyfill-php84 (1.x-dev d8ced4d): Extracting archive
  - Installing doctrine/collections (2.4.x-dev 0cc9698): Extracting archive
  - Installing doctrine/orm (3.6.x-dev c1f7a60): Extracting archive
  - Installing masterminds/html5 (2.10.0): Extracting archive
  - Installing mtdowling/jmespath.php (dev-master a2a865e): Extracting archive
  - Installing psr/clock (1.0.0): Extracting archive
  - Installing symfony/type-info (8.0.x-dev cbab7d4): Extracting archive
  - Installing symfony/property-info (8.0.x-dev bde1637): Extracting archive
  - Installing symfony/property-access (8.0.x-dev 74df691): Extracting archive
  - Installing symfony/polyfill-intl-icu (1.x-dev bfc8fa1): Extracting archive
  - Installing symfony/options-resolver (8.0.x-dev 17b3cb5): Extracting archive
  - Installing symfony/form (8.0.x-dev 5320af1): Extracting archive
  - Installing symfony/process (7.4.x-dev 410c833): Extracting archive
  - Installing nikic/php-parser (dev-master f103601): Extracting archive
  - Installing symfony/maker-bundle (1.x-dev c86da84): Extracting archive
  - Installing symfony/phpunit-bridge (8.0.x-dev b77163b): Extracting archive
  - Installing symfony/polyfill-php81 (1.x-dev 4a4cfc2): Extracting archive
  - Installing symfony/password-hasher (8.0.x-dev 80e7c7e): Extracting archive
  - Installing symfony/security-core (8.0.x-dev 7cc6cd2): Extracting archive
  - Installing symfony/security-http (8.0.x-dev f99eb69): Extracting archive
  - Installing symfony/security-csrf (8.0.x-dev 2790fe6): Extracting archive
  - Installing symfony/clock (8.0.x-dev be52243): Extracting archive
  - Installing symfony/security-bundle (8.0.x-dev db7907a): Extracting archive
  - Installing symfony/translation-contracts (dev-main 65a8bc8): Extracting archive
  - Installing twig/twig (3.x-dev b1d2a9c): Extracting archive
  - Installing symfony/twig-bridge (8.0.x-dev b068de9): Extracting archive
  - Installing symfony/twig-bundle (8.0.x-dev 5f79218): Extracting archive
  - Installing symfony/polyfill-uuid (1.x-dev 21533be): Extracting archive
  - Installing symfony/uid (8.0.x-dev 600055f): Extracting archive
  - Installing zenstruck/callback (1.x-dev 42be837): Extracting archive
  - Installing zenstruck/assert (1.x-dev b9a37a5): Extracting archive
  - Installing symfony/dom-crawler (7.4.x-dev 7b9b135): Extracting archive
  - Installing symfony/css-selector (7.4.x-dev 57500fe): Extracting archive
  - Installing symfony/browser-kit (7.4.x-dev 7f5f0fb): Extracting archive
  - Installing behat/mink (dev-master 916471d): Extracting archive
  - Installing zenstruck/browser (1.x-dev 615bed9): Extracting archive
  - Installing fakerphp/faker (1.24.x-dev f7ac507): Extracting archive
  - Installing zenstruck/foundry (2.x-dev 0a65872): Extracting archive
Generating autoload files
63 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Symfony recipes are disabled: "symfony/flex" not found in the root composer.json

Synchronizing package.json is disabled: "symfony/flex" not found in the root composer.json
+ '[' Autocomplete = LiveComponent ']'
+ vendor/bin/simple-phpunit
No composer.json in current directory, to use the one at /home/runner/work/ux/ux/src/Autocomplete run interactively or set config.use-parent-dir to true
No composer.json found in the current directory, showing available packages from packagist.org
Creating a "phpunit/phpunit" project at "./phpunit-9.6-0"
Installing phpunit/phpunit (9.6.24)
Plugins have been disabled.
  - Installing phpunit/phpunit (9.6.24): Extracting archive
Created project in /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0
phpspec/prophecy is not required in your composer.json and has not been removed
composer.json has been updated
composer.json has been updated
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 2 updates, 0 removals
  - Upgrading doctrine/instantiator (1.5.0 => 2.0.0)
  - Upgrading nikic/php-parser (v4.19.4 => v5.6.1)
  - Locking symfony/phpunit-bridge (9.6.99)
Writing lock file
Installing dependencies from lock file
Package operations: 28 installs, 0 updates, 0 removals
  - Installing doctrine/instantiator (2.0.0): Extracting archive
  - Installing myclabs/deep-copy (1.13.4): Extracting archive
  - Installing phar-io/version (3.2.1): Extracting archive
  - Installing phar-io/manifest (2.0.4): Extracting archive
  - Installing theseer/tokenizer (1.2.3): Extracting archive
  - Installing sebastian/version (3.0.2): Extracting archive
  - Installing nikic/php-parser (v5.6.1): Extracting archive
  - Installing sebastian/lines-of-code (1.0.4): Extracting archive
  - Installing sebastian/environment (5.1.5): Extracting archive
  - Installing sebastian/complexity (2.0.3): Extracting archive
  - Installing sebastian/code-unit-reverse-lookup (2.0.3): Extracting archive
  - Installing phpunit/php-text-template (2.0.4): Extracting archive
  - Installing phpunit/php-file-iterator (3.0.6): Extracting archive
  - Installing phpunit/php-code-coverage (9.2.32): Extracting archive
  - Installing phpunit/php-invoker (3.1.1): Extracting archive
  - Installing phpunit/php-timer (5.0.3): Extracting archive
  - Installing sebastian/cli-parser (1.0.2): Extracting archive
  - Installing sebastian/code-unit (1.0.8): Extracting archive
  - Installing sebastian/recursion-context (4.0.6): Extracting archive
  - Installing sebastian/exporter (4.0.6): Extracting archive
  - Installing sebastian/diff (4.0.6): Extracting archive
  - Installing sebastian/comparator (4.0.9): Extracting archive
  - Installing sebastian/object-reflector (2.0.4): Extracting archive
  - Installing sebastian/global-state (5.0.8): Extracting archive
  - Installing sebastian/object-enumerator (4.0.4): Extracting archive
  - Installing sebastian/resource-operations (3.0.4): Extracting archive
  - Installing sebastian/type (3.2.1): Extracting archive
  - Installing symfony/phpunit-bridge (9.6.99): Symlinking from ../../../../vendor/symfony/phpunit-bridge
Generating optimized autoload files
25 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Symfony recipes are disabled: "symfony/flex" not found in the root composer.json

Synchronizing package.json is disabled: "symfony/flex" not found in the root composer.json
PHPUnit 9.6.24 by Sebastian Bergmann and contributors.

Testing
ESSSSS......................I...............                      44 / 44 (100%)

Time: 00:12.848, Memory: 62.50 MB

There was 1 error:

1) Symfony\UX\Autocomplete\Tests\Functional\AutocompleteFormRenderingTest::testFieldsRenderWithStimulusController
RuntimeException: Unable to create the Doctrine Proxy directory "". in /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php:49
Stack trace:
#0 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php(96): Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer->warmUp()
#1 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/http-kernel/Kernel.php(546): Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate->warmUp()
#2 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/http-kernel/Kernel.php(744): Symfony\Component\HttpKernel\Kernel->initializeContainer()
symfony#3 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/http-kernel/Kernel.php(120): Symfony\Component\HttpKernel\Kernel->preBoot()
symfony#4 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/framework-bundle/Test/KernelTestCase.php(75): Symfony\Component\HttpKernel\Kernel->boot()
symfony#5 /home/runner/work/ux/ux/src/Autocomplete/vendor/zenstruck/foundry/src/Test/ResetDatabase.php(36): Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::bootKernel()
symfony#6 /home/runner/work/ux/ux/src/Autocomplete/vendor/zenstruck/foundry/src/Persistence/ResetDatabase/ResetDatabaseManager.php(51): Symfony\UX\Autocomplete\Tests\Functional\AutocompleteFormRenderingTest::{closure:Zenstruck\Foundry\Test\ResetDatabase::_resetDatabaseBeforeFirstTest():36}()
symfony#7 /home/runner/work/ux/ux/src/Autocomplete/vendor/zenstruck/foundry/src/Test/ResetDatabase.php(35): Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager::resetBeforeFirstTest()
symfony#8 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/Framework/TestSuite.php(629): Symfony\UX\Autocomplete\Tests\Functional\AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest()
symfony#9 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/Framework/TestSuite.php(685): PHPUnit\Framework\TestSuite->run()
symfony#10 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/Framework/TestSuite.php(685): PHPUnit\Framework\TestSuite->run()
symfony#11 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/TextUI/TestRunner.php(651): PHPUnit\Framework\TestSuite->run()
symfony#12 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/TextUI/Command.php(146): PHPUnit\TextUI\TestRunner->run()
symfony#13 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/src/TextUI/Command.php(99): PHPUnit\TextUI\Command->run()
symfony#14 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/.phpunit/phpunit-9.6-0/phpunit(22): PHPUnit\TextUI\Command::main()
symfony#15 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php(461): include('...')
symfony#16 /home/runner/work/ux/ux/src/Autocomplete/vendor/symfony/phpunit-bridge/bin/simple-phpunit(13): require('...')
symfony#17 /home/runner/work/ux/ux/src/Autocomplete/vendor/bin/simple-phpunit(119): include('...')
symfony#18 {main}
ERRORS!
Tests: 44, Assertions: 166, Errors: 1, Skipped: 5, Incomplete: 1.

Remaining indirect deprecation notices (118)

  110x: Class "Doctrine\ORM\Proxy\Autoloader" is deprecated. Use native lazy objects instead. (Autoloader.php:74 called by DoctrineBundle.php:130, doctrine/orm#12005, package doctrine/orm)
    8x in FieldAutocompleterTest::_resetDatabaseBeforeEachTest from Symfony\UX\Autocomplete\Tests\Functional
    7x in CustomAutocompleterTest::_resetDatabaseBeforeEachTest from Symfony\UX\Autocomplete\Tests\Functional
    7x in EntityMetadataTest::_resetDatabaseBeforeEachTest from Symfony\UX\Autocomplete\Tests\Integration\Doctrine
    4x in CustomAutocompleterTest::testItReturnsBasicResults from Symfony\UX\Autocomplete\Tests\Functional
    4x in CustomAutocompleterTest::testItOnlySearchedOnSearchableFields from Symfony\UX\Autocomplete\Tests\Functional
    ...

  2x: Since symfony/framework-bundle 7.3: Not setting the "property_info.with_constructor_extractor" option explicitly is deprecated because its default value will change in version 8.0.
    2x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional

  2x: is_dir(): Passing null to parameter #1 ($filename) of type string is deprecated
    2x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional

  1x: Since doctrine/doctrine-bundle 2.13: Enabling the controller resolver automapping feature has been deprecated. Symfony Mapped Route Parameters should be used as replacement.
    1x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional

  1x: Since zenstruck/foundry 2.5: Not setting "zenstruck_foundry.persistence.flush_once" to true is deprecated. This option will be forced to true in 3.0
    1x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional

  1x: Calling Doctrine\ORM\Configuration::getProxyDir is deprecated and will not be possible in Doctrine ORM 4.0. (Configuration.php:85 called by ProxyCacheWarmer.php:47, doctrine/orm#12005, package doctrine/orm)
    1x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional

  1x: mkdir(): Passing null to parameter #1 ($directory) of type string is deprecated
    1x in AutocompleteFormRenderingTest::_resetDatabaseBeforeFirstTest from Symfony\UX\Autocomplete\Tests\Functional
Job exited with: 2

Error: KO Autocomplete
```

_Caused_ by:
- https://github.com/symfony/doctrine-bridge/blob/0174ad0/CacheWarmer/ProxyCacheWarmer.php#L47-L50
- https://github.com/doctrine/DoctrineBundle/blob/3a854f8/src/DependencyInjection/DoctrineExtension.php#L705-L717

cc `@Kbond` `@derrabus`

Commits
-------

f7117f5 [CI] Fix highest dev jobs
Kocal pushed a commit that referenced this pull request Jul 22, 2026
[Toolkit][Bootstrap] Harden Offcanvas a11y wiring and Toast attributes

Offcanvas now mirrors Modal's provide/inject pattern with Offcanvas:Header,
Offcanvas:Title and Offcanvas:Body sub-components. The title id is auto-wired
to the root aria-labelledby, so a custom header can no longer leave a dangling
reference (previously consumers had to repeat "<id>-label" by hand).

Toast defaults `delay` to null and emits data-bs-delay only when it is set,
dropping the hard-coded 5000 magic number, and clamps `role`/`live` to their
allowed values like the other enum props in the kit.

Adjust code previews height

Migrate documentation & examples to the new format

diff --git a/src/Toolkit/CHANGELOG.md b/src/Toolkit/CHANGELOG.md
index c7b1423329a..29eb1aae2ea 100644
--- a/src/Toolkit/CHANGELOG.md
+++ b/src/Toolkit/CHANGELOG.md
@@ -2,11 +2,12 @@

 ## 3.4.0

-- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
+- [Bootstrap] Add the Bootstrap kit
 - [Common] Add the `common` kit, with design-system agnostic `logout-link` and `post-link` recipes
+- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
 - Add per-recipe `README.md` documentation
-- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits
 - Add optional `color` and `icon` fields to the kit manifest
+- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits

 ## 3.3.0

diff --git a/src/Toolkit/kits/bootstrap/INSTALL.md b/src/Toolkit/kits/bootstrap/INSTALL.md
new file mode 100644
index 00000000000..049d781d3cc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/INSTALL.md
@@ -0,0 +1,32 @@
+# Getting started
+
+This kit provides ready-to-use Twig components based on [Bootstrap 5.3](https://getbootstrap.com/docs/5.3/).
+
+## Requirements
+
+Bootstrap's CSS is required by all components. Bootstrap's JavaScript is also required by interactive components and examples.
+
+## Installation
+
+Install Bootstrap with AssetMapper:
+
+```bash
+php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css
+```
+
+Then import Bootstrap from `assets/app.js`:
+
+```js
+import 'bootstrap/dist/css/bootstrap.min.css';
+import 'bootstrap';
+```
+
+With npm, pnpm, or Yarn, install Bootstrap instead with your package manager:
+
+```bash
+npm install bootstrap@^5.3.0
+```
+
+The imports in `assets/app.js` remain the same.
+
+And that's it! You can now use the Bootstrap Twig components in your templates.
diff --git a/src/Toolkit/kits/bootstrap/accordion/README.md b/src/Toolkit/kits/bootstrap/accordion/README.md
new file mode 100644
index 00000000000..091b51a9b06
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/README.md
@@ -0,0 +1,102 @@
+# Accordion
+
+Build vertically collapsing sections powered by Bootstrap's Collapse plugin.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="faq-accordion">
+    <twig:Accordion:Item id="faq-first" label="What is Symfony UX?" expanded>
+        <strong>Symfony UX connects Symfony with modern frontend tools.</strong>
+        It provides JavaScript packages, Twig components, and integrations designed to work naturally with Symfony applications.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="faq-second" label="Can I customize these components?">
+        Yes. Pass HTML attributes and Bootstrap utility classes directly to the Twig components, or customize Bootstrap through Sass and CSS variables.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="faq-third" label="Does the accordion support multiple open items?">
+        Set <code>alwaysOpen</code> on the root accordion to omit Bootstrap's parent constraint.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Accordion id="account-accordion">
+    <twig:Accordion:Item id="account-profile" label="Profile" expanded>
+        Update your personal details and contact information.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="account-security" label="Security">
+        Review your password and two-factor authentication settings.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Accessibility
+
+Choose a `headingTag` that fits the surrounding document hierarchy. Each heading contains a button with `aria-expanded` and `aria-controls`, and each panel references its heading with `aria-labelledby`.
+
+Bootstrap's transition respects `prefers-reduced-motion`. Keep labels concise and do not hide essential information exclusively inside collapsed sections.
+
+## Examples
+
+Render a conventional accordion where opening one item closes the currently open item.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-example">
+    <twig:Accordion:Item id="accordion-item-one" label="Accordion Item #1" expanded>
+        <strong>This is the first item's accordion body.</strong>
+        It is shown by default until the Collapse plugin updates the appropriate classes and ARIA state.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="accordion-item-two" label="Accordion Item #2">
+        <strong>This is the second item's accordion body.</strong>
+        It is hidden by default and can contain nearly any HTML content.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="accordion-item-three" label="Accordion Item #3">
+        <strong>This is the third item's accordion body.</strong>
+        Opening it closes the currently open item.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Flush
+
+Remove outer borders and rounded corners for an edge-to-edge accordion.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-flush-example" flush>
+    <twig:Accordion:Item id="flush-item-one" label="Accordion Item #1">
+        Placeholder content for this edge-to-edge accordion item.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="flush-item-two" label="Accordion Item #2">
+        The flush style removes some borders and rounded corners.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="flush-item-three" label="Accordion Item #3">
+        Items retain the same Collapse behavior and accessibility attributes.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Always open
+
+Allow several items to remain expanded by omitting Bootstrap's parent constraint.
+
+```twig {"preview":true,"height":"430px"}
+<twig:Accordion id="accordion-always-open" alwaysOpen>
+    <twig:Accordion:Item id="always-open-one" label="Accordion Item #1" expanded>
+        This item starts open and remains open when another item is expanded.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="always-open-two" label="Accordion Item #2">
+        Multiple items can stay open because no <code>data-bs-parent</code> constraint is rendered.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="always-open-three" label="Accordion Item #3">
+        Each button still controls its own panel and synchronizes <code>aria-expanded</code>.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/accordion/manifest.json b/src/Toolkit/kits/bootstrap/accordion/manifest.json
new file mode 100644
index 00000000000..0c313137099
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Accordion",
+    "description": "Build vertically collapsing sections powered by Bootstrap's Collapse plugin.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["symfony/ux-twig-component:^3.1", "twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
new file mode 100644
index 00000000000..0959ef8e4fe
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
@@ -0,0 +1,16 @@
+{# @prop id string The unique identifier used to coordinate the accordion items. #}
+{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
+{# @prop alwaysOpen boolean Whether multiple accordion items may remain open. #}
+{# @block content The accordion items, typically `Accordion:Item` components. #}
+{%- props id, flush = false, alwaysOpen = false -%}
+{%- do provide('accordion.id', id) -%}
+{%- do provide('accordion.alwaysOpen', alwaysOpen) -%}
+<div {{ attributes.defaults({
+    id: id,
+    class: html_classes({
+        accordion: true,
+        'accordion-flush': flush,
+    }),
+}) }}>
+    {%- block content %}{% endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
new file mode 100644
index 00000000000..9ddada634c7
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
@@ -0,0 +1,43 @@
+{# @prop id string The unique identifier used for the heading and collapsible panel. #}
+{# @prop label string The text displayed in the accordion button. #}
+{# @prop expanded boolean Whether the accordion item is initially expanded. #}
+{# @prop headingTag 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' The heading element to render. #}
+{# @block label The accordion button label. #}
+{# @block content The content displayed inside the collapsible panel. #}
+{%- props id, label = '', expanded = false, headingTag = 'h2' -%}
+{%- set _accordion_id = inject('accordion.id', null) -%}
+{%- set _accordion_always_open = inject('accordion.alwaysOpen', false) -%}
+{%- set heading_id = id ~ '-heading' -%}
+{%- set collapse_id = id ~ '-collapse' -%}
+{%- set final_heading_tag = headingTag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] ? headingTag : 'h2' -%}
+<div {{ attributes.defaults({class: 'accordion-item'}) }}>
+    <{{ final_heading_tag }} class="accordion-header" id="{{ heading_id }}">
+        <button
+            class="{{ html_classes({
+                'accordion-button': true,
+                collapsed: not expanded,
+            }) }}"
+            type="button"
+            data-bs-toggle="collapse"
+            data-bs-target="#{{ collapse_id }}"
+            aria-expanded="{{ expanded ? 'true' : 'false' }}"
+            aria-controls="{{ collapse_id }}"
+        >
+            {%- block label %}{{ label }}{% endblock -%}
+        </button>
+    </{{ final_heading_tag }}>
+    <div
+        id="{{ collapse_id }}"
+        class="{{ html_classes({
+            'accordion-collapse': true,
+            collapse: true,
+            show: expanded,
+        }) }}"
+        aria-labelledby="{{ heading_id }}"
+        {% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent="#{{ _accordion_id }}"{% endif %}
+    >
+        <div class="accordion-body">
+            {%- block content %}{% endblock -%}
+        </div>
+    </div>
+</div>
diff --git a/src/Toolkit/kits/bootstrap/alert/README.md b/src/Toolkit/kits/bootstrap/alert/README.md
new file mode 100644
index 00000000000..de97d720d76
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/README.md
@@ -0,0 +1,131 @@
+# Alert
+
+Provides contextual feedback messages for typical user actions.
+
+```twig {"preview":true,"height":"210px"}
+<twig:Alert color="success" heading="Well done!" dismissible>
+    Your changes were saved successfully. <a href="#" class="alert-link">Review them now</a>.
+</twig:Alert>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Alert>A simple primary alert - check it out!</twig:Alert>
+```
+
+## Accessibility
+
+Do not rely on color alone to communicate the alert's meaning. Include clear visible text or additional context for assistive technologies.
+
+Dismissal removes the alert from the document. When appropriate, listen for Bootstrap's `closed.bs.alert` event and move focus to a logical destination.
+
+## Examples
+
+### Contextual variants
+
+Use one of Bootstrap's eight contextual colors to match the message's purpose.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="mb-0">A simple primary alert - check it out!</twig:Alert>
+    <twig:Alert color="secondary" class="mb-0">A simple secondary alert - check it out!</twig:Alert>
+    <twig:Alert color="success" class="mb-0">A simple success alert - check it out!</twig:Alert>
+    <twig:Alert color="danger" class="mb-0">A simple danger alert - check it out!</twig:Alert>
+    <twig:Alert color="warning" class="mb-0">A simple warning alert - check it out!</twig:Alert>
+    <twig:Alert color="info" class="mb-0">A simple info alert - check it out!</twig:Alert>
+    <twig:Alert color="light" class="mb-0">A simple light alert - check it out!</twig:Alert>
+    <twig:Alert color="dark" class="mb-0">A simple dark alert - check it out!</twig:Alert>
+</div>
+```
+
+### Link color
+
+Use `alert-link` for links that inherit a suitable color from their alert variant.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="mb-0">A simple primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="secondary" class="mb-0">A simple secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="success" class="mb-0">A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="danger" class="mb-0">A simple danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="warning" class="mb-0">A simple warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="info" class="mb-0">A simple info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="light" class="mb-0">A simple light alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="dark" class="mb-0">A simple dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+</div>
+```
+
+### Additional content
+
+Alerts can contain headings, paragraphs, dividers, and other structured content.
+
+```twig {"preview":true,"height":"300px"}
+<twig:Alert color="success" heading="Well done!">
+    <p>Aww yeah, you successfully read this important alert message. This example text is long enough to show how spacing works with additional content.</p>
+    <hr>
+    <p class="mb-0">Whenever you need to, use margin utilities to keep things nice and tidy.</p>
+</twig:Alert>
+```
+
+### Icons
+
+Combine alerts with flex utilities and accessible inline icons.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="d-flex align-items-center mb-0">
+        <svg xmlns="http://www.w3.org/2000/svg" class="bi flex-shrink-0 me-2" width="16" height="16" viewBox="0 0 16 16" role="img" aria-label="Warning:">
+            <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+        </svg>
+        <div>An example alert with an icon</div>
+    </twig:Alert>
+
+    <svg xmlns="http://www.w3.org/2000/svg" class="d-none">
+        <symbol id="check-circle-fill" viewBox="0 0 16 16">
+            <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
+        </symbol>
+        <symbol id="info-fill" viewBox="0 0 16 16">
+            <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" />
+        </symbol>
+        <symbol id="exclamation-triangle-fill" viewBox="0 0 16 16">
+            <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+        </symbol>
+    </svg>
+
+    <twig:Alert class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Info:"><use href="#info-fill" /></svg>
+        <div>An example alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="success" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Success:"><use href="#check-circle-fill" /></svg>
+        <div>An example success alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="warning" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Warning:"><use href="#exclamation-triangle-fill" /></svg>
+        <div>An example warning alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="danger" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:"><use href="#exclamation-triangle-fill" /></svg>
+        <div>An example danger alert with an icon</div>
+    </twig:Alert>
+</div>
+```
+
+### Dismissing
+
+Enable Bootstrap's alert plugin with a close button, dismissal classes, and transition classes.
+
+```twig {"preview":true,"height":"180px"}
+<twig:Alert color="warning" dismissible>
+    <strong>Holy guacamole!</strong> You should check in on some of those fields below.
+</twig:Alert>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/alert/manifest.json b/src/Toolkit/kits/bootstrap/alert/manifest.json
new file mode 100644
index 00000000000..439a7ea7b77
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Alert",
+    "description": "Provides contextual feedback messages for typical user actions.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
new file mode 100644
index 00000000000..deaecf3b74a
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
@@ -0,0 +1,39 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop dismissible boolean Whether to render a dismiss button and dismissal transition classes. #}
+{# @prop heading string|null The optional alert heading. #}
+{# @prop content string The fallback message when no content block is provided. #}
+{# @block heading The alert heading, rendered before the main content. #}
+{# @block content The alert message and optional additional content. #}
+{# @block close The dismiss control rendered when the alert is dismissible. #}
+{%- props
+    color = 'primary',
+    dismissible = false,
+    heading = null,
+    content = ''
+-%}
+
+{%- set classes = html_classes({
+    alert: true,
+    ('alert-' ~ color): true,
+    'alert-dismissible': dismissible,
+    fade: dismissible,
+    show: dismissible,
+}) -%}
+
+<div {{ attributes.defaults({class: classes, role: 'alert'}) }}>
+    {%- block heading -%}
+        {%- if heading is not null and heading is not empty -%}
+            <h4 class="alert-heading">{{ heading }}</h4>
+        {%- endif -%}
+    {%- endblock -%}
+
+    {%- block content -%}
+        {{- content -}}
+    {%- endblock -%}
+
+    {%- if dismissible -%}
+        {%- block close -%}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        {%- endblock -%}
+    {%- endif -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/badge/README.md b/src/Toolkit/kits/bootstrap/badge/README.md
new file mode 100644
index 00000000000..0e98fe71e50
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/README.md
@@ -0,0 +1,121 @@
+# Badge
+
+A small count or label used to highlight status, counts, or short contextual information.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap align-items-center gap-2">
+    <twig:Badge color="primary">New</twig:Badge>
+    <twig:Badge color="success">Published</twig:Badge>
+    <twig:Badge color="warning">Pending</twig:Badge>
+    <twig:Badge color="danger" pill>99+</twig:Badge>
+    <button type="button" class="btn btn-primary position-relative ms-2">
+        Inbox
+        <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+            12
+            <span class="visually-hidden">unread messages</span>
+        </twig:Badge>
+    </button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Badge>New</twig:Badge>
+```
+
+## Accessibility
+
+Do not rely on badge color alone to communicate meaning. Include meaningful visible text or additional context with Bootstrap's `visually-hidden` utility.
+
+When a badge contains a count, make sure its relationship to the surrounding heading, button, or link is clear to assistive technologies.
+
+## Examples
+
+### Headings
+
+Badges inherit their size from the immediate parent, so they scale naturally inside headings.
+
+```twig {"preview":true,"height":"350px"}
+<h1>Example heading <twig:Badge color="secondary">New</twig:Badge></h1>
+<h2>Example heading <twig:Badge color="secondary">New</twig:Badge></h2>
+<h3>Example heading <twig:Badge color="secondary">New</twig:Badge></h3>
+<h4>Example heading <twig:Badge color="secondary">New</twig:Badge></h4>
+<h5>Example heading <twig:Badge color="secondary">New</twig:Badge></h5>
+<h6>Example heading <twig:Badge color="secondary">New</twig:Badge></h6>
+```
+
+### Buttons
+
+Place a badge inside a button to display a related count.
+
+```twig {"preview":true,"height":"140px"}
+<button type="button" class="btn btn-primary">
+    Notifications <twig:Badge color="secondary">4</twig:Badge>
+</button>
+```
+
+### Positioned
+
+Combine badges with Bootstrap's position utilities to create counters and status indicators.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex align-items-center gap-5">
+    <button type="button" class="btn btn-primary position-relative">
+        Inbox
+        <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+            99+
+            <span class="visually-hidden">unread messages</span>
+        </twig:Badge>
+    </button>
+
+    <button type="button" class="btn btn-primary position-relative">
+        Profile
+        <span class="position-absolute top-0 start-100 translate-middle p-2 bg-danger border border-light rounded-circle">
+            <span class="visually-hidden">New alerts</span>
+        </span>
+    </button>
+</div>
+```
+
+### Background colors
+
+Use contextual background colors while keeping the badge's meaning clear from its text.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Badge color="primary">Primary</twig:Badge>
+    <twig:Badge color="secondary">Secondary</twig:Badge>
+    <twig:Badge color="success">Success</twig:Badge>
+    <twig:Badge color="danger">Danger</twig:Badge>
+    <twig:Badge color="warning">Warning</twig:Badge>
+    <twig:Badge color="info">Info</twig:Badge>
+    <twig:Badge color="light">Light</twig:Badge>
+    <twig:Badge color="dark">Dark</twig:Badge>
+</div>
+```
+
+### Pill badges
+
+Use the pill style for a more rounded badge with a larger border radius.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Badge color="primary" pill>Primary</twig:Badge>
+    <twig:Badge color="secondary" pill>Secondary</twig:Badge>
+    <twig:Badge color="success" pill>Success</twig:Badge>
+    <twig:Badge color="danger" pill>Danger</twig:Badge>
+    <twig:Badge color="warning" pill>Warning</twig:Badge>
+    <twig:Badge color="info" pill>Info</twig:Badge>
+    <twig:Badge color="light" pill>Light</twig:Badge>
+    <twig:Badge color="dark" pill>Dark</twig:Badge>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/badge/manifest.json b/src/Toolkit/kits/bootstrap/badge/manifest.json
new file mode 100644
index 00000000000..5de93966a98
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Badge",
+    "description": "A small count or label used to highlight status, counts, or short contextual information.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
new file mode 100644
index 00000000000..feed2b1e604
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
@@ -0,0 +1,21 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop pill boolean Whether to use Bootstrap's rounded pill style. #}
+{# @prop label string The fallback badge text when no content block is provided. #}
+{# @block content The badge text and optional inline content. #}
+{%- props
+    color = 'primary',
+    pill = false,
+    label = ''
+-%}
+
+{%- set classes = html_classes({
+    badge: true,
+    ('text-bg-' ~ color): true,
+    'rounded-pill': pill,
+}) -%}
+
+<span {{ attributes.defaults({class: classes}) }}>
+    {%- block content -%}
+        {{- label -}}
+    {%- endblock -%}
+</span>
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/README.md b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
new file mode 100644
index 00000000000..32f9ae2ab22
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
@@ -0,0 +1,78 @@
+# Breadcrumb
+
+Indicates the current page's location within a navigational hierarchy.
+
+```twig {"preview":true,"height":"150px"}
+<twig:Breadcrumb>
+    <li class="breadcrumb-item"><a href="#">Home</a></li>
+    <li class="breadcrumb-item"><a href="#">Library</a></li>
+    <li class="breadcrumb-item active" aria-current="page">Data</li>
+</twig:Breadcrumb>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Breadcrumb>
+    <li class="breadcrumb-item"><a href="#">Home</a></li>
+    <li class="breadcrumb-item active" aria-current="page">Library</li>
+</twig:Breadcrumb>
+```
+
+## Accessibility
+
+Give the navigation a meaningful accessible name. Apply `aria-current="page"` to the final breadcrumb item so assistive technologies can identify the current page.
+
+## Examples
+
+Build breadcrumb trails with linked items followed by an active item for the current page.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item active" aria-current="page">Home</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item"><a href="#">Library</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Data</li>
+    </twig:Breadcrumb>
+</div>
+```
+
+### Dividers
+
+Customize the divider with text, an escaped SVG data URL, or an empty CSS custom property.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Breadcrumb divider=">">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb style="--bs-breadcrumb-divider: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E&quot;);">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb style="--bs-breadcrumb-divider: ''">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
new file mode 100644
index 00000000000..403cad75101
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
@@ -0,0 +1,10 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Breadcrumb",
+    "description": "Indicates the current page's location within a navigational hierarchy.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
new file mode 100644
index 00000000000..f612e2ae9fd
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
@@ -0,0 +1,21 @@
+{# @prop divider string|null The custom text used as the divider between breadcrumb items. #}
+{# @prop label string|null The accessible name for the breadcrumb navigation. #}
+{# @block content The ordered list of breadcrumb items. #}
+{%- props
+    divider = null,
+    label = 'breadcrumb'
+-%}
+
+{%- set default_attrs = {} -%}
+{%- if label is not null -%}
+    {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+{%- if divider is not null -%}
+    {%- set default_attrs = default_attrs|merge({style: "--bs-breadcrumb-divider: '" ~ divider ~ "';"}) -%}
+{%- endif -%}
+
+<nav {{ attributes.defaults(default_attrs) }}>
+    <ol class="breadcrumb">
+        {%- block content -%}{%- endblock -%}
+    </ol>
+</nav>
diff --git a/src/Toolkit/kits/bootstrap/button-group/README.md b/src/Toolkit/kits/bootstrap/button-group/README.md
new file mode 100644
index 00000000000..a171ddf64bc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/README.md
@@ -0,0 +1,267 @@
+# Button Group
+
+Groups a series of buttons on a single line or in a vertical column.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Primary actions">
+        <twig:Button>Left</twig:Button>
+        <twig:Button>Middle</twig:Button>
+        <twig:Button>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Mixed actions">
+        <twig:Button color="danger">Delete</twig:Button>
+        <twig:Button color="warning">Archive</twig:Button>
+        <twig:Button color="success">Publish</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:ButtonGroup label="Basic example">
+    <twig:Button>Left</twig:Button>
+    <twig:Button>Middle</twig:Button>
+    <twig:Button>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+## Accessibility
+
+Button groups need `role="group"` and an explicit accessible name. The `label` prop supplies `aria-label`; set it to `null` only when providing an equivalent `aria-labelledby` attribute.
+
+Use `role="toolbar"` and a meaningful label on toolbars that combine multiple button groups.
+
+## Examples
+
+### Basic example
+
+Group buttons or links together as one related set of controls.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Basic example">
+        <twig:Button>Left</twig:Button>
+        <twig:Button>Middle</twig:Button>
+        <twig:Button>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Page navigation">
+        <twig:Button tag="a" href="#" class="active" aria-current="page">Active link</twig:Button>
+        <twig:Button tag="a" href="#">Link</twig:Button>
+        <twig:Button tag="a" href="#">Link</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Mixed styles
+
+Mix contextual button styles within one group.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic mixed styles example">
+    <twig:Button color="danger">Left</twig:Button>
+    <twig:Button color="warning">Middle</twig:Button>
+    <twig:Button color="success">Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Outlined styles
+
+Use outline buttons for a group with less visual weight.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic outlined example">
+    <twig:Button outline>Left</twig:Button>
+    <twig:Button outline>Middle</twig:Button>
+    <twig:Button outline>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Checkbox and radio button groups
+
+Combine Bootstrap toggle inputs and labels into seamless checkbox or radio groups.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Basic checkbox toggle button group">
+        <input type="checkbox" class="btn-check" id="btncheck1" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck1">Checkbox 1</label>
+        <input type="checkbox" class="btn-check" id="btncheck2" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck2">Checkbox 2</label>
+        <input type="checkbox" class="btn-check" id="btncheck3" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck3">Checkbox 3</label>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Basic radio toggle button group">
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked>
+        <label class="btn btn-outline-primary" for="btnradio1">Radio 1</label>
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btnradio2">Radio 2</label>
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btnradio3">Radio 3</label>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Button toolbar
+
+Combine several button groups, and optionally input groups, inside a labeled toolbar.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column gap-3">
+    <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
+        <twig:ButtonGroup label="First group" class="me-2">
+            <twig:Button>1</twig:Button>
+            <twig:Button>2</twig:Button>
+            <twig:Button>3</twig:Button>
+            <twig:Button>4</twig:Button>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Second group" class="me-2">
+            <twig:Button color="secondary">5</twig:Button>
+            <twig:Button color="secondary">6</twig:Button>
+            <twig:Button color="secondary">7</twig:Button>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Third group">
+            <twig:Button color="info">8</twig:Button>
+        </twig:ButtonGroup>
+    </div>
+
+    <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with input group">
+        <twig:ButtonGroup label="First group" class="me-2">
+            <twig:Button color="secondary" outline>1</twig:Button>
+            <twig:Button color="secondary" outline>2</twig:Button>
+            <twig:Button color="secondary" outline>3</twig:Button>
+            <twig:Button color="secondary" outline>4</twig:Button>
+        </twig:ButtonGroup>
+        <div class="input-group">
+            <div class="input-group-text" id="btnGroupAddon">@</div>
+            <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon">
+        </div>
+    </div>
+
+    <div class="btn-toolbar justify-content-between" role="toolbar" aria-label="Toolbar with justified input group">
+        <twig:ButtonGroup label="First group">
+            <twig:Button color="secondary" outline>1</twig:Button>
+            <twig:Button color="secondary" outline>2</twig:Button>
+            <twig:Button color="secondary" outline>3</twig:Button>
+            <twig:Button color="secondary" outline>4</twig:Button>
+        </twig:ButtonGroup>
+        <div class="input-group">
+            <div class="input-group-text" id="btnGroupAddon2">@</div>
+            <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon2">
+        </div>
+    </div>
+</div>
+```
+
+### Sizing
+
+Apply a size to the group instead of repeating it on every button.
+
+```twig {"preview":true,"height":"230px"}
+<div class="d-flex flex-column align-items-start gap-2">
+    <twig:ButtonGroup size="lg" label="Large button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Default button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup size="sm" label="Small button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Nesting
+
+Nest a button group to place a dropdown alongside other buttons.
+
+```twig {"preview":true,"height":"260px"}
+<twig:ButtonGroup label="Button group with nested dropdown">
+    <twig:Button>1</twig:Button>
+    <twig:Button>2</twig:Button>
+    <twig:ButtonGroup label="Dropdown menu">
+        <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+        <ul class="dropdown-menu">
+            <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+        </ul>
+    </twig:ButtonGroup>
+</twig:ButtonGroup>
+```
+
+### Vertical variation
+
+Stack buttons, dropdowns, or radio controls in a vertical group.
+
+```twig {"preview":true,"height":"320px"}
+<div class="d-flex flex-wrap align-items-start gap-4">
+    <twig:ButtonGroup vertical label="Vertical button group">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup vertical label="Vertical button group with dropdowns">
+        <twig:ButtonGroup label="Dropdown menu">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:ButtonGroup label="Dropstart menu" class="dropstart">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Dropend menu" class="dropend">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Dropup menu" class="dropup">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup vertical label="Vertical radio toggle button group">
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked>
+        <label class="btn btn-outline-danger" for="vbtn-radio1">Radio 1</label>
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off">
+        <label class="btn btn-outline-danger" for="vbtn-radio2">Radio 2</label>
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio3" autocomplete="off">
+        <label class="btn btn-outline-danger" for="vbtn-radio3">Radio 3</label>
+    </twig:ButtonGroup>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button-group/manifest.json b/src/Toolkit/kits/bootstrap/button-group/manifest.json
new file mode 100644
index 00000000000..1bf2a3a363b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Button Group",
+    "description": "Groups a series of buttons on a single line or in a vertical column.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
new file mode 100644
index 00000000000..3bae952f3a3
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
@@ -0,0 +1,22 @@
+{# @prop vertical boolean Whether to stack the buttons vertically. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap size applied to every button in the group. #}
+{# @prop label string|null The accessible name for the group. #}
+{# @block content The buttons, links, or form controls contained in the group. #}
+{%- props
+    vertical = false,
+    size = null,
+    label = 'Button group'
+-%}
+
+{%- set classes = html_classes({
+    (vertical ? 'btn-group-vertical' : 'btn-group'): true,
+    ('btn-group-' ~ size): size in ['lg', 'sm'],
+}) -%}
+{%- set default_attrs = {class: classes, role: 'group'} -%}
+{%- if label not in [null, ''] -%}
+    {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+
+<div {{ attributes.defaults(default_attrs) }}>
+    {%- block content -%}{%- endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/button/README.md b/src/Toolkit/kits/bootstrap/button/README.md
new file mode 100644
index 00000000000..f2c5d177f7d
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/README.md
@@ -0,0 +1,197 @@
+# Button
+
+Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button>Primary</twig:Button>
+    <twig:Button color="secondary">Secondary</twig:Button>
+    <twig:Button color="success">Success</twig:Button>
+    <twig:Button color="danger">Danger</twig:Button>
+    <twig:Button color="warning">Warning</twig:Button>
+    <twig:Button color="info">Info</twig:Button>
+    <twig:Button color="light">Light</twig:Button>
+    <twig:Button color="dark">Dark</twig:Button>
+    <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Button>Save changes</twig:Button>
+```
+
+## Accessibility
+
+Do not rely on button color alone to communicate meaning. Use explicit labels or additional visually hidden text when needed.
+
+Disabled links omit their `href`, expose `aria-disabled="true"`, and are removed from keyboard navigation.
+
+## Examples
+
+### Base class
+
+Use the base Bootstrap button class without a contextual color when defining a custom style.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button :color="null">Base class</twig:Button>
+```
+
+### Variants
+
+Use contextual colors to communicate the purpose of an action.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button>Primary</twig:Button>
+    <twig:Button color="secondary">Secondary</twig:Button>
+    <twig:Button color="success">Success</twig:Button>
+    <twig:Button color="danger">Danger</twig:Button>
+    <twig:Button color="warning">Warning</twig:Button>
+    <twig:Button color="info">Info</twig:Button>
+    <twig:Button color="light">Light</twig:Button>
+    <twig:Button color="dark">Dark</twig:Button>
+    <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+### Disable text wrapping
+
+Add Bootstrap's `text-nowrap` utility when a button label must remain on one line.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button class="text-nowrap">This button label does not wrap</twig:Button>
+```
+
+### Button tags
+
+Render the component as a button, link, or input depending on the semantic element required.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button tag="a" href="#">Link</twig:Button>
+    <twig:Button type="submit">Button</twig:Button>
+    <twig:Button tag="input" value="Input" />
+    <twig:Button tag="input" type="submit" value="Submit" />
+    <twig:Button tag="input" type="reset" value="Reset" />
+</div>
+```
+
+### Outline buttons
+
+Use outline styles for actions that need less visual weight.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button outline>Primary</twig:Button>
+    <twig:Button color="secondary" outline>Secondary</twig:Button>
+    <twig:Button color="success" outline>Success</twig:Button>
+    <twig:Button color="danger" outline>Danger</twig:Button>
+    <twig:Button color="warning" outline>Warning</twig:Button>
+    <twig:Button color="info" outline>Info</twig:Button>
+    <twig:Button color="light" outline>Light</twig:Button>
+    <twig:Button color="dark" outline>Dark</twig:Button>
+</div>
+```
+
+### Sizes
+
+Use Bootstrap's large and small sizes, or customize the component with Bootstrap CSS variables.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button size="lg">Large button</twig:Button>
+        <twig:Button color="secondary" size="lg">Large button</twig:Button>
+    </div>
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button size="sm">Small button</twig:Button>
+        <twig:Button color="secondary" size="sm">Small button</twig:Button>
+    </div>
+    <twig:Button style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">
+        Custom button
+    </twig:Button>
+</div>
+```
+
+### Disabled state
+
+Disable buttons and links while preserving the appropriate HTML and accessibility semantics.
+
+```twig {"preview":true,"height":"200px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button disabled>Primary button</twig:Button>
+        <twig:Button color="secondary" disabled>Button</twig:Button>
+        <twig:Button outline disabled>Primary button</twig:Button>
+        <twig:Button color="secondary" outline disabled>Button</twig:Button>
+    </div>
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button tag="a" disabled>Primary link</twig:Button>
+        <twig:Button tag="a" color="secondary" disabled>Link</twig:Button>
+    </div>
+</div>
+```
+
+### Block buttons
+
+Combine the component with Bootstrap layout utilities to create responsive full-width buttons.
+
+```twig {"preview":true,"height":"470px"}
+<div class="d-flex flex-column gap-4">
+    <div class="d-grid gap-2">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 d-md-block">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 col-6 mx-auto">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 d-md-flex justify-content-md-end">
+        <twig:Button class="me-md-2">Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+</div>
+```
+
+### Toggle states
+
+Use Bootstrap's button plugin for controls that toggle between pressed and unpressed states.
+
+```twig {"preview":true,"height":"300px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button :color="null" data-bs-toggle="button">Toggle button</twig:Button>
+        <twig:Button :color="null" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+        <twig:Button :color="null" disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button data-bs-toggle="button">Toggle button</twig:Button>
+        <twig:Button class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+        <twig:Button disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button tag="a" :color="null" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+        <twig:Button tag="a" :color="null" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+        <twig:Button tag="a" :color="null" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button tag="a" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+        <twig:Button tag="a" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+        <twig:Button tag="a" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+    </div>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button/manifest.json b/src/Toolkit/kits/bootstrap/button/manifest.json
new file mode 100644
index 00000000000..582b11f728e
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Button",
+    "description": "Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
new file mode 100644
index 00000000000..dfbdedd725b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
@@ -0,0 +1,64 @@
+{# @prop tag 'button'|'a'|'input' The HTML element to render. #}
+{# @prop type 'button'|'submit'|'reset'|null The type used for button and input elements. #}
+{# @prop href string|null The destination used for anchor elements. #}
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'link'|null The Bootstrap contextual color. #}
+{# @prop outline boolean Whether to use an outline style. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap button size. #}
+{# @prop disabled boolean Whether the control is disabled. #}
+{# @prop value string|null The value rendered by input elements. #}
+{# @prop content string The fallback label when no content block is provided. #}
+{# @block content The button label and optional inline content. #}
+{%- props
+    tag = 'button',
+    type = null,
+    href = null,
+    color = 'primary',
+    outline = false,
+    size = null,
+    disabled = false,
+    value = null,
+    content = ''
+-%}
+
+{%- set final_tag = tag|lower in ['a', 'input'] ? tag|lower : 'button' -%}
+{%- set final_type = type in ['button', 'submit', 'reset'] ? type : 'button' -%}
+{%- set valid_colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'link'] -%}
+{%- set classes = html_classes({
+    btn: true,
+    ('btn-' ~ (outline and color != 'link' ? 'outline-' : '') ~ color): color in valid_colors,
+    ('btn-' ~ size): size in ['lg', 'sm'],
+    disabled: disabled and final_tag == 'a',
+}) -%}
+{%- set default_attrs = {class: classes} -%}
+
+{%- if final_tag == 'a' -%}
+    {%- set default_attrs = default_attrs|merge({role: 'button'}) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({'aria-disabled': 'true', tabindex: '-1'}) -%}
+    {%- else -%}
+        {%- set default_attrs = default_attrs|merge({href: href not in [null, ''] ? href : '#'}) -%}
+    {%- endif -%}
+{%- elseif final_tag == 'input' -%}
+    {%- set default_attrs = default_attrs|merge({
+        type: final_type,
+        value: value ?? content,
+    }) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+    {%- endif -%}
+{%- else -%}
+    {%- set default_attrs = default_attrs|merge({type: final_type}) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+    {%- endif -%}
+{%- endif -%}
+
+{%- if final_tag == 'input' -%}
+    <input {{ attributes.defaults(default_attrs) }}>
+{%- else -%}
+    <{{ final_tag }} {{ attributes.defaults(default_attrs) }}>
+        {%- block content -%}
+            {{- content -}}
+        {%- endblock -%}
+    </{{ final_tag }}>
+{%- endif -%}
diff --git a/src/Toolkit/kits/bootstrap/card/README.md b/src/Toolkit/kits/bootstrap/card/README.md
new file mode 100644
index 00000000000..fbde5eab9b6
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/card/README.md
@@ -0,0 +1,357 @@
+# Card
+
+Build flexible content containers with optional headers, footers, images, and contextual styles.
+
+```twig {"preview":true,"height":"480px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 22rem;">
+    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+    <twig:Card:Body>
+        <twig:Card:Title>Explore the outdoors</twig:Card:Title>
+        <twig:Card:Subtitle class="mb-2 text-body-secondary">Weekend inspiration</twig:Card:Subtitle>
+        <twig:Card:Text>Discover a new trail and make time for a change of scenery.</twig:Card:Text>
+        <a href="#" class="btn btn-primary">Find a trail</a>
+        <twig:Card:Link href="#">View guide</twig:Card:Link>
+    </twig:Card:Body>
+    <twig:Card:Footer class="text-body-secondary">Updated today</twig:Card:Footer>
+</twig:Card>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Card style="max-width: 24rem;">
+    <twig:Card:Header>Featured</twig:Card:Header>
+    <twig:Card:Body>
+        <twig:Card:Title>Card title</twig:Card:Title>
+        <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+    </twig:Card:Body>
+    <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+</twig:Card>
+```
+
+## Accessibility
+
+Choose a semantic heading level for each card title that fits the surrounding page hierarchy.
+
+Do not rely on background, text, or border color alone to communicate meaning. Keep sufficient contrast and provide an equivalent text cue when color carries information.
+
+## Examples
+
+Combine an image, title, supporting text, and action in a fixed-width card.
+
+```twig {"preview":true,"height":"400px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 18rem;">
+    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+    <twig:Card:Body>
+        <twig:Card:Title>Card title</twig:Card:Title>
+        <twig:Card:Text>Some quick example text to build on the card title and make up the bulk of the card's content.</twig:Card:Text>
+        <a href="#" class="btn btn-primary">Go somewhere</a>
+    </twig:Card:Body>
+</twig:Card>
+```
+
+### Content types
+
+Mix card bodies, titles, subtitles, text, links, images, and list groups.
+
+```twig {"preview":true,"height":"470px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<div class="d-flex flex-wrap gap-3">
+    <twig:Card style="width: 18rem;">
+        <twig:Card:Body>
+            <twig:Card:Title>Card title</twig:Card:Title>
+            <twig:Card:Subtitle class="mb-2 text-body-secondary">Card subtitle</twig:Card:Subtitle>
+            <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+            <twig:Card:Link href="#">Card link</twig:Card:Link>
+            <twig:Card:Link href="#">Another link</twig:Card:Link>
+        </twig:Card:Body>
+    </twig:Card>
+
+    <twig:Card style="width: 18rem;">
+        <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+        <twig:Card:Body>
+            <twig:Card:Text>Text, images, list groups, and links can be freely combined.</twig:Card:Text>
+        </twig:Card:Body>
+        <ul class="list-group list-group-flush">
+            <li class="list-group-item">An item</li>
+            <li class="list-group-item">A second item</li>
+            <li class="list-group-item">A third item</li>
+        </ul>
+        <twig:Card:Body>
+            <twig:Card:Link href="#">Card link</twig:Card:Link>
+        </twig:Card:Body>
+    </twig:Card>
+</div>
+```
+
+### Header and footer
+
+Add optional headers and footers, including semantic heading and quote content.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-grid gap-3">
+    <twig:Card>
+        <twig:Card:Header>Featured</twig:Card:Header>
+        <twig:Card:Body>
+            <twig:Card:Title>Special title treatment</twig:Card:Title>
+            <twig:Card:Text>With supporting text below as a natural lead-in to additional content.</twig:Card:Text>
+            <a href="#" class="btn btn-primary">Go somewhere</a>
+        </twig:Card:Body>
+    </twig:Card>
+
+    <twig:Card class="text-center">
+        <twig:Card:Header tag="h5">Featured</twig:Card:Header>
+        <twig:Card:Body>
+            <figure class="mb-0">
+                <blockquote class="blockquote"><p>A well-known quote, contained in a blockquote element.</p></blockquote>
+                <figcaption class="blockquote-footer mb-0">Someone famous in <cite title="Source Title">Source Title</cite></figcaption>
+            </figure>
+        </twig:Card:Body>
+        <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+    </twig:Card>
+</div>
+```
+
+### Sizing
+
+Size cards with Bootstrap's grid, width utilities, or custom CSS.
+
+```twig {"preview":true,"height":"190px"}
+<div class="row">
+    <div class="col-sm-6 mb-3 mb-sm-0">
+        <twig:Card>
+            <twig:Card:Body>
+                <twig:Card:Title>Grid-sized card</twig:Card:Title>
+                <twig:Card:Text>The surrounding Bootstrap grid controls this card's width.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+    <div class="col-sm-6">
+        <twig:Card class="w-75">
+            <twig:Card:Body>
+                <twig:Card:Title>Utility-sized card</twig:Card:Title>
+                <twig:Card:Text>Width utilities or custom CSS can size a card directly.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+</div>
+```
+
+### Text alignment
+
+Apply Bootstrap text alignment utilities to a card or one of its sections.
+
+```twig {"preview":true,"height":"440px"}
+<div class="d-flex flex-wrap gap-3">
+    {% for alignment, label in {'': 'Start', 'text-center': 'Center', 'text-end': 'End'} %}
+        <twig:Card class="{{ alignment }}" style="width: 18rem;">
+            <twig:Card:Body>
+                <twig:Card:Title>{{ label }} aligned</twig:Card:Title>
+                <twig:Card:Text>Use Bootstrap text utilities on the whole card or an individual section.</twig:Card:Text>
+                <a href="#" class="btn btn-primary">Go somewhere</a>
+            </twig:Card:Body>
+        </twig:Card>
+    {% endfor %}
+</div>
+```
+
+### Navigation
+
+Place tabs or pills in the card header.
+
+```twig {"preview":true,"height":"350px"}
+<div class="d-grid gap-3">
+    {% for style in ['tabs', 'pills'] %}
+        <twig:Card class="text-center">
+            <twig:Card:Header>
+                <ul class="nav nav-{{ style }} card-header-{{ style }}">
+                    <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
+                    <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+                    <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+                </ul>
+            </twig:Card:Header>
+            <twig:Card:Body>
+                <twig:Card:Title>Special title treatment</twig:Card:Title>
+                <twig:Card:Text>Card headers can contain Bootstrap tab or pill navigation.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    {% endfor %}
+</div>
+```
+
+### Image caps
+
+Position an image at the top or bottom edge of a card.
+
+```twig {"preview":true,"height":"350px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=960&q=80' %}
+
+<div class="row row-cols-1 row-cols-sm-2 g-3">
+    <div class="col">
+        <twig:Card class="h-100">
+            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+            <twig:Card:Body>
+                <twig:Card:Title>Top image cap</twig:Card:Title>
+                <twig:Card:Text>Images can sit at either end of a card.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+    <div class="col">
+        <twig:Card class="h-100">
+            <twig:Card:Body>
+                <twig:Card:Title>Bottom image cap</twig:Card:Title>
+                <twig:Card:Text>The bottom position matches the lower card corners.</twig:Card:Text>
+            </twig:Card:Body>
+            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" position="bottom" />
+        </twig:Card>
+    </div>
+</div>
+```
+
+### Image overlays
+
+Use an image as the card background and place concise, high-contrast content over it.
+
+```twig {"preview":true,"height":"410px"}
+{% set image_url = 'https://images.unsplash.com/p…
Kocal pushed a commit that referenced this pull request Jul 22, 2026
[Toolkit][Bootstrap] Harden Offcanvas a11y wiring and Toast attributes

Offcanvas now mirrors Modal's provide/inject pattern with Offcanvas:Header,
Offcanvas:Title and Offcanvas:Body sub-components. The title id is auto-wired
to the root aria-labelledby, so a custom header can no longer leave a dangling
reference (previously consumers had to repeat "<id>-label" by hand).

Toast defaults `delay` to null and emits data-bs-delay only when it is set,
dropping the hard-coded 5000 magic number, and clamps `role`/`live` to their
allowed values like the other enum props in the kit.

Adjust code previews height

Migrate documentation & examples to the new format

diff --git a/src/Toolkit/CHANGELOG.md b/src/Toolkit/CHANGELOG.md
index c7b1423329a..29eb1aae2ea 100644
--- a/src/Toolkit/CHANGELOG.md
+++ b/src/Toolkit/CHANGELOG.md
@@ -2,11 +2,12 @@

 ## 3.4.0

-- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
+- [Bootstrap] Add the Bootstrap kit
 - [Common] Add the `common` kit, with design-system agnostic `logout-link` and `post-link` recipes
+- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
 - Add per-recipe `README.md` documentation
-- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits
 - Add optional `color` and `icon` fields to the kit manifest
+- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits

 ## 3.3.0

diff --git a/src/Toolkit/kits/bootstrap/INSTALL.md b/src/Toolkit/kits/bootstrap/INSTALL.md
new file mode 100644
index 00000000000..049d781d3cc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/INSTALL.md
@@ -0,0 +1,32 @@
+# Getting started
+
+This kit provides ready-to-use Twig components based on [Bootstrap 5.3](https://getbootstrap.com/docs/5.3/).
+
+## Requirements
+
+Bootstrap's CSS is required by all components. Bootstrap's JavaScript is also required by interactive components and examples.
+
+## Installation
+
+Install Bootstrap with AssetMapper:
+
+```bash
+php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css
+```
+
+Then import Bootstrap from `assets/app.js`:
+
+```js
+import 'bootstrap/dist/css/bootstrap.min.css';
+import 'bootstrap';
+```
+
+With npm, pnpm, or Yarn, install Bootstrap instead with your package manager:
+
+```bash
+npm install bootstrap@^5.3.0
+```
+
+The imports in `assets/app.js` remain the same.
+
+And that's it! You can now use the Bootstrap Twig components in your templates.
diff --git a/src/Toolkit/kits/bootstrap/accordion/README.md b/src/Toolkit/kits/bootstrap/accordion/README.md
new file mode 100644
index 00000000000..091b51a9b06
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/README.md
@@ -0,0 +1,102 @@
+# Accordion
+
+Build vertically collapsing sections powered by Bootstrap's Collapse plugin.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="faq-accordion">
+    <twig:Accordion:Item id="faq-first" label="What is Symfony UX?" expanded>
+        <strong>Symfony UX connects Symfony with modern frontend tools.</strong>
+        It provides JavaScript packages, Twig components, and integrations designed to work naturally with Symfony applications.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="faq-second" label="Can I customize these components?">
+        Yes. Pass HTML attributes and Bootstrap utility classes directly to the Twig components, or customize Bootstrap through Sass and CSS variables.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="faq-third" label="Does the accordion support multiple open items?">
+        Set <code>alwaysOpen</code> on the root accordion to omit Bootstrap's parent constraint.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Accordion id="account-accordion">
+    <twig:Accordion:Item id="account-profile" label="Profile" expanded>
+        Update your personal details and contact information.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="account-security" label="Security">
+        Review your password and two-factor authentication settings.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Accessibility
+
+Choose a `headingTag` that fits the surrounding document hierarchy. Each heading contains a button with `aria-expanded` and `aria-controls`, and each panel references its heading with `aria-labelledby`.
+
+Bootstrap's transition respects `prefers-reduced-motion`. Keep labels concise and do not hide essential information exclusively inside collapsed sections.
+
+## Examples
+
+Render a conventional accordion where opening one item closes the currently open item.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-example">
+    <twig:Accordion:Item id="accordion-item-one" label="Accordion Item #1" expanded>
+        <strong>This is the first item's accordion body.</strong>
+        It is shown by default until the Collapse plugin updates the appropriate classes and ARIA state.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="accordion-item-two" label="Accordion Item #2">
+        <strong>This is the second item's accordion body.</strong>
+        It is hidden by default and can contain nearly any HTML content.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="accordion-item-three" label="Accordion Item #3">
+        <strong>This is the third item's accordion body.</strong>
+        Opening it closes the currently open item.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Flush
+
+Remove outer borders and rounded corners for an edge-to-edge accordion.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-flush-example" flush>
+    <twig:Accordion:Item id="flush-item-one" label="Accordion Item #1">
+        Placeholder content for this edge-to-edge accordion item.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="flush-item-two" label="Accordion Item #2">
+        The flush style removes some borders and rounded corners.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="flush-item-three" label="Accordion Item #3">
+        Items retain the same Collapse behavior and accessibility attributes.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Always open
+
+Allow several items to remain expanded by omitting Bootstrap's parent constraint.
+
+```twig {"preview":true,"height":"430px"}
+<twig:Accordion id="accordion-always-open" alwaysOpen>
+    <twig:Accordion:Item id="always-open-one" label="Accordion Item #1" expanded>
+        This item starts open and remains open when another item is expanded.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="always-open-two" label="Accordion Item #2">
+        Multiple items can stay open because no <code>data-bs-parent</code> constraint is rendered.
+    </twig:Accordion:Item>
+    <twig:Accordion:Item id="always-open-three" label="Accordion Item #3">
+        Each button still controls its own panel and synchronizes <code>aria-expanded</code>.
+    </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/accordion/manifest.json b/src/Toolkit/kits/bootstrap/accordion/manifest.json
new file mode 100644
index 00000000000..0c313137099
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Accordion",
+    "description": "Build vertically collapsing sections powered by Bootstrap's Collapse plugin.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["symfony/ux-twig-component:^3.1", "twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
new file mode 100644
index 00000000000..0959ef8e4fe
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
@@ -0,0 +1,16 @@
+{# @prop id string The unique identifier used to coordinate the accordion items. #}
+{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
+{# @prop alwaysOpen boolean Whether multiple accordion items may remain open. #}
+{# @block content The accordion items, typically `Accordion:Item` components. #}
+{%- props id, flush = false, alwaysOpen = false -%}
+{%- do provide('accordion.id', id) -%}
+{%- do provide('accordion.alwaysOpen', alwaysOpen) -%}
+<div {{ attributes.defaults({
+    id: id,
+    class: html_classes({
+        accordion: true,
+        'accordion-flush': flush,
+    }),
+}) }}>
+    {%- block content %}{% endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
new file mode 100644
index 00000000000..9ddada634c7
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
@@ -0,0 +1,43 @@
+{# @prop id string The unique identifier used for the heading and collapsible panel. #}
+{# @prop label string The text displayed in the accordion button. #}
+{# @prop expanded boolean Whether the accordion item is initially expanded. #}
+{# @prop headingTag 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' The heading element to render. #}
+{# @block label The accordion button label. #}
+{# @block content The content displayed inside the collapsible panel. #}
+{%- props id, label = '', expanded = false, headingTag = 'h2' -%}
+{%- set _accordion_id = inject('accordion.id', null) -%}
+{%- set _accordion_always_open = inject('accordion.alwaysOpen', false) -%}
+{%- set heading_id = id ~ '-heading' -%}
+{%- set collapse_id = id ~ '-collapse' -%}
+{%- set final_heading_tag = headingTag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] ? headingTag : 'h2' -%}
+<div {{ attributes.defaults({class: 'accordion-item'}) }}>
+    <{{ final_heading_tag }} class="accordion-header" id="{{ heading_id }}">
+        <button
+            class="{{ html_classes({
+                'accordion-button': true,
+                collapsed: not expanded,
+            }) }}"
+            type="button"
+            data-bs-toggle="collapse"
+            data-bs-target="#{{ collapse_id }}"
+            aria-expanded="{{ expanded ? 'true' : 'false' }}"
+            aria-controls="{{ collapse_id }}"
+        >
+            {%- block label %}{{ label }}{% endblock -%}
+        </button>
+    </{{ final_heading_tag }}>
+    <div
+        id="{{ collapse_id }}"
+        class="{{ html_classes({
+            'accordion-collapse': true,
+            collapse: true,
+            show: expanded,
+        }) }}"
+        aria-labelledby="{{ heading_id }}"
+        {% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent="#{{ _accordion_id }}"{% endif %}
+    >
+        <div class="accordion-body">
+            {%- block content %}{% endblock -%}
+        </div>
+    </div>
+</div>
diff --git a/src/Toolkit/kits/bootstrap/alert/README.md b/src/Toolkit/kits/bootstrap/alert/README.md
new file mode 100644
index 00000000000..de97d720d76
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/README.md
@@ -0,0 +1,131 @@
+# Alert
+
+Provides contextual feedback messages for typical user actions.
+
+```twig {"preview":true,"height":"210px"}
+<twig:Alert color="success" heading="Well done!" dismissible>
+    Your changes were saved successfully. <a href="#" class="alert-link">Review them now</a>.
+</twig:Alert>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Alert>A simple primary alert - check it out!</twig:Alert>
+```
+
+## Accessibility
+
+Do not rely on color alone to communicate the alert's meaning. Include clear visible text or additional context for assistive technologies.
+
+Dismissal removes the alert from the document. When appropriate, listen for Bootstrap's `closed.bs.alert` event and move focus to a logical destination.
+
+## Examples
+
+### Contextual variants
+
+Use one of Bootstrap's eight contextual colors to match the message's purpose.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="mb-0">A simple primary alert - check it out!</twig:Alert>
+    <twig:Alert color="secondary" class="mb-0">A simple secondary alert - check it out!</twig:Alert>
+    <twig:Alert color="success" class="mb-0">A simple success alert - check it out!</twig:Alert>
+    <twig:Alert color="danger" class="mb-0">A simple danger alert - check it out!</twig:Alert>
+    <twig:Alert color="warning" class="mb-0">A simple warning alert - check it out!</twig:Alert>
+    <twig:Alert color="info" class="mb-0">A simple info alert - check it out!</twig:Alert>
+    <twig:Alert color="light" class="mb-0">A simple light alert - check it out!</twig:Alert>
+    <twig:Alert color="dark" class="mb-0">A simple dark alert - check it out!</twig:Alert>
+</div>
+```
+
+### Link color
+
+Use `alert-link` for links that inherit a suitable color from their alert variant.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="mb-0">A simple primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="secondary" class="mb-0">A simple secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="success" class="mb-0">A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="danger" class="mb-0">A simple danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="warning" class="mb-0">A simple warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="info" class="mb-0">A simple info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="light" class="mb-0">A simple light alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+    <twig:Alert color="dark" class="mb-0">A simple dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+</div>
+```
+
+### Additional content
+
+Alerts can contain headings, paragraphs, dividers, and other structured content.
+
+```twig {"preview":true,"height":"300px"}
+<twig:Alert color="success" heading="Well done!">
+    <p>Aww yeah, you successfully read this important alert message. This example text is long enough to show how spacing works with additional content.</p>
+    <hr>
+    <p class="mb-0">Whenever you need to, use margin utilities to keep things nice and tidy.</p>
+</twig:Alert>
+```
+
+### Icons
+
+Combine alerts with flex utilities and accessible inline icons.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Alert class="d-flex align-items-center mb-0">
+        <svg xmlns="http://www.w3.org/2000/svg" class="bi flex-shrink-0 me-2" width="16" height="16" viewBox="0 0 16 16" role="img" aria-label="Warning:">
+            <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+        </svg>
+        <div>An example alert with an icon</div>
+    </twig:Alert>
+
+    <svg xmlns="http://www.w3.org/2000/svg" class="d-none">
+        <symbol id="check-circle-fill" viewBox="0 0 16 16">
+            <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
+        </symbol>
+        <symbol id="info-fill" viewBox="0 0 16 16">
+            <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" />
+        </symbol>
+        <symbol id="exclamation-triangle-fill" viewBox="0 0 16 16">
+            <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+        </symbol>
+    </svg>
+
+    <twig:Alert class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Info:"><use href="#info-fill" /></svg>
+        <div>An example alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="success" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Success:"><use href="#check-circle-fill" /></svg>
+        <div>An example success alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="warning" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Warning:"><use href="#exclamation-triangle-fill" /></svg>
+        <div>An example warning alert with an icon</div>
+    </twig:Alert>
+    <twig:Alert color="danger" class="d-flex align-items-center mb-0">
+        <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:"><use href="#exclamation-triangle-fill" /></svg>
+        <div>An example danger alert with an icon</div>
+    </twig:Alert>
+</div>
+```
+
+### Dismissing
+
+Enable Bootstrap's alert plugin with a close button, dismissal classes, and transition classes.
+
+```twig {"preview":true,"height":"180px"}
+<twig:Alert color="warning" dismissible>
+    <strong>Holy guacamole!</strong> You should check in on some of those fields below.
+</twig:Alert>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/alert/manifest.json b/src/Toolkit/kits/bootstrap/alert/manifest.json
new file mode 100644
index 00000000000..439a7ea7b77
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Alert",
+    "description": "Provides contextual feedback messages for typical user actions.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
new file mode 100644
index 00000000000..deaecf3b74a
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
@@ -0,0 +1,39 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop dismissible boolean Whether to render a dismiss button and dismissal transition classes. #}
+{# @prop heading string|null The optional alert heading. #}
+{# @prop content string The fallback message when no content block is provided. #}
+{# @block heading The alert heading, rendered before the main content. #}
+{# @block content The alert message and optional additional content. #}
+{# @block close The dismiss control rendered when the alert is dismissible. #}
+{%- props
+    color = 'primary',
+    dismissible = false,
+    heading = null,
+    content = ''
+-%}
+
+{%- set classes = html_classes({
+    alert: true,
+    ('alert-' ~ color): true,
+    'alert-dismissible': dismissible,
+    fade: dismissible,
+    show: dismissible,
+}) -%}
+
+<div {{ attributes.defaults({class: classes, role: 'alert'}) }}>
+    {%- block heading -%}
+        {%- if heading is not null and heading is not empty -%}
+            <h4 class="alert-heading">{{ heading }}</h4>
+        {%- endif -%}
+    {%- endblock -%}
+
+    {%- block content -%}
+        {{- content -}}
+    {%- endblock -%}
+
+    {%- if dismissible -%}
+        {%- block close -%}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        {%- endblock -%}
+    {%- endif -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/badge/README.md b/src/Toolkit/kits/bootstrap/badge/README.md
new file mode 100644
index 00000000000..0e98fe71e50
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/README.md
@@ -0,0 +1,121 @@
+# Badge
+
+A small count or label used to highlight status, counts, or short contextual information.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap align-items-center gap-2">
+    <twig:Badge color="primary">New</twig:Badge>
+    <twig:Badge color="success">Published</twig:Badge>
+    <twig:Badge color="warning">Pending</twig:Badge>
+    <twig:Badge color="danger" pill>99+</twig:Badge>
+    <button type="button" class="btn btn-primary position-relative ms-2">
+        Inbox
+        <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+            12
+            <span class="visually-hidden">unread messages</span>
+        </twig:Badge>
+    </button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Badge>New</twig:Badge>
+```
+
+## Accessibility
+
+Do not rely on badge color alone to communicate meaning. Include meaningful visible text or additional context with Bootstrap's `visually-hidden` utility.
+
+When a badge contains a count, make sure its relationship to the surrounding heading, button, or link is clear to assistive technologies.
+
+## Examples
+
+### Headings
+
+Badges inherit their size from the immediate parent, so they scale naturally inside headings.
+
+```twig {"preview":true,"height":"350px"}
+<h1>Example heading <twig:Badge color="secondary">New</twig:Badge></h1>
+<h2>Example heading <twig:Badge color="secondary">New</twig:Badge></h2>
+<h3>Example heading <twig:Badge color="secondary">New</twig:Badge></h3>
+<h4>Example heading <twig:Badge color="secondary">New</twig:Badge></h4>
+<h5>Example heading <twig:Badge color="secondary">New</twig:Badge></h5>
+<h6>Example heading <twig:Badge color="secondary">New</twig:Badge></h6>
+```
+
+### Buttons
+
+Place a badge inside a button to display a related count.
+
+```twig {"preview":true,"height":"140px"}
+<button type="button" class="btn btn-primary">
+    Notifications <twig:Badge color="secondary">4</twig:Badge>
+</button>
+```
+
+### Positioned
+
+Combine badges with Bootstrap's position utilities to create counters and status indicators.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex align-items-center gap-5">
+    <button type="button" class="btn btn-primary position-relative">
+        Inbox
+        <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+            99+
+            <span class="visually-hidden">unread messages</span>
+        </twig:Badge>
+    </button>
+
+    <button type="button" class="btn btn-primary position-relative">
+        Profile
+        <span class="position-absolute top-0 start-100 translate-middle p-2 bg-danger border border-light rounded-circle">
+            <span class="visually-hidden">New alerts</span>
+        </span>
+    </button>
+</div>
+```
+
+### Background colors
+
+Use contextual background colors while keeping the badge's meaning clear from its text.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Badge color="primary">Primary</twig:Badge>
+    <twig:Badge color="secondary">Secondary</twig:Badge>
+    <twig:Badge color="success">Success</twig:Badge>
+    <twig:Badge color="danger">Danger</twig:Badge>
+    <twig:Badge color="warning">Warning</twig:Badge>
+    <twig:Badge color="info">Info</twig:Badge>
+    <twig:Badge color="light">Light</twig:Badge>
+    <twig:Badge color="dark">Dark</twig:Badge>
+</div>
+```
+
+### Pill badges
+
+Use the pill style for a more rounded badge with a larger border radius.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Badge color="primary" pill>Primary</twig:Badge>
+    <twig:Badge color="secondary" pill>Secondary</twig:Badge>
+    <twig:Badge color="success" pill>Success</twig:Badge>
+    <twig:Badge color="danger" pill>Danger</twig:Badge>
+    <twig:Badge color="warning" pill>Warning</twig:Badge>
+    <twig:Badge color="info" pill>Info</twig:Badge>
+    <twig:Badge color="light" pill>Light</twig:Badge>
+    <twig:Badge color="dark" pill>Dark</twig:Badge>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/badge/manifest.json b/src/Toolkit/kits/bootstrap/badge/manifest.json
new file mode 100644
index 00000000000..5de93966a98
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Badge",
+    "description": "A small count or label used to highlight status, counts, or short contextual information.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
new file mode 100644
index 00000000000..feed2b1e604
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
@@ -0,0 +1,21 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop pill boolean Whether to use Bootstrap's rounded pill style. #}
+{# @prop label string The fallback badge text when no content block is provided. #}
+{# @block content The badge text and optional inline content. #}
+{%- props
+    color = 'primary',
+    pill = false,
+    label = ''
+-%}
+
+{%- set classes = html_classes({
+    badge: true,
+    ('text-bg-' ~ color): true,
+    'rounded-pill': pill,
+}) -%}
+
+<span {{ attributes.defaults({class: classes}) }}>
+    {%- block content -%}
+        {{- label -}}
+    {%- endblock -%}
+</span>
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/README.md b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
new file mode 100644
index 00000000000..32f9ae2ab22
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
@@ -0,0 +1,78 @@
+# Breadcrumb
+
+Indicates the current page's location within a navigational hierarchy.
+
+```twig {"preview":true,"height":"150px"}
+<twig:Breadcrumb>
+    <li class="breadcrumb-item"><a href="#">Home</a></li>
+    <li class="breadcrumb-item"><a href="#">Library</a></li>
+    <li class="breadcrumb-item active" aria-current="page">Data</li>
+</twig:Breadcrumb>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Breadcrumb>
+    <li class="breadcrumb-item"><a href="#">Home</a></li>
+    <li class="breadcrumb-item active" aria-current="page">Library</li>
+</twig:Breadcrumb>
+```
+
+## Accessibility
+
+Give the navigation a meaningful accessible name. Apply `aria-current="page"` to the final breadcrumb item so assistive technologies can identify the current page.
+
+## Examples
+
+Build breadcrumb trails with linked items followed by an active item for the current page.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item active" aria-current="page">Home</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb>
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item"><a href="#">Library</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Data</li>
+    </twig:Breadcrumb>
+</div>
+```
+
+### Dividers
+
+Customize the divider with text, an escaped SVG data URL, or an empty CSS custom property.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+    <twig:Breadcrumb divider=">">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb style="--bs-breadcrumb-divider: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E&quot;);">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+
+    <twig:Breadcrumb style="--bs-breadcrumb-divider: ''">
+        <li class="breadcrumb-item"><a href="#">Home</a></li>
+        <li class="breadcrumb-item active" aria-current="page">Library</li>
+    </twig:Breadcrumb>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
new file mode 100644
index 00000000000..403cad75101
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
@@ -0,0 +1,10 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Breadcrumb",
+    "description": "Indicates the current page's location within a navigational hierarchy.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
new file mode 100644
index 00000000000..f612e2ae9fd
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
@@ -0,0 +1,21 @@
+{# @prop divider string|null The custom text used as the divider between breadcrumb items. #}
+{# @prop label string|null The accessible name for the breadcrumb navigation. #}
+{# @block content The ordered list of breadcrumb items. #}
+{%- props
+    divider = null,
+    label = 'breadcrumb'
+-%}
+
+{%- set default_attrs = {} -%}
+{%- if label is not null -%}
+    {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+{%- if divider is not null -%}
+    {%- set default_attrs = default_attrs|merge({style: "--bs-breadcrumb-divider: '" ~ divider ~ "';"}) -%}
+{%- endif -%}
+
+<nav {{ attributes.defaults(default_attrs) }}>
+    <ol class="breadcrumb">
+        {%- block content -%}{%- endblock -%}
+    </ol>
+</nav>
diff --git a/src/Toolkit/kits/bootstrap/button-group/README.md b/src/Toolkit/kits/bootstrap/button-group/README.md
new file mode 100644
index 00000000000..a171ddf64bc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/README.md
@@ -0,0 +1,267 @@
+# Button Group
+
+Groups a series of buttons on a single line or in a vertical column.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Primary actions">
+        <twig:Button>Left</twig:Button>
+        <twig:Button>Middle</twig:Button>
+        <twig:Button>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Mixed actions">
+        <twig:Button color="danger">Delete</twig:Button>
+        <twig:Button color="warning">Archive</twig:Button>
+        <twig:Button color="success">Publish</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:ButtonGroup label="Basic example">
+    <twig:Button>Left</twig:Button>
+    <twig:Button>Middle</twig:Button>
+    <twig:Button>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+## Accessibility
+
+Button groups need `role="group"` and an explicit accessible name. The `label` prop supplies `aria-label`; set it to `null` only when providing an equivalent `aria-labelledby` attribute.
+
+Use `role="toolbar"` and a meaningful label on toolbars that combine multiple button groups.
+
+## Examples
+
+### Basic example
+
+Group buttons or links together as one related set of controls.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Basic example">
+        <twig:Button>Left</twig:Button>
+        <twig:Button>Middle</twig:Button>
+        <twig:Button>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Page navigation">
+        <twig:Button tag="a" href="#" class="active" aria-current="page">Active link</twig:Button>
+        <twig:Button tag="a" href="#">Link</twig:Button>
+        <twig:Button tag="a" href="#">Link</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Mixed styles
+
+Mix contextual button styles within one group.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic mixed styles example">
+    <twig:Button color="danger">Left</twig:Button>
+    <twig:Button color="warning">Middle</twig:Button>
+    <twig:Button color="success">Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Outlined styles
+
+Use outline buttons for a group with less visual weight.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic outlined example">
+    <twig:Button outline>Left</twig:Button>
+    <twig:Button outline>Middle</twig:Button>
+    <twig:Button outline>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Checkbox and radio button groups
+
+Combine Bootstrap toggle inputs and labels into seamless checkbox or radio groups.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <twig:ButtonGroup label="Basic checkbox toggle button group">
+        <input type="checkbox" class="btn-check" id="btncheck1" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck1">Checkbox 1</label>
+        <input type="checkbox" class="btn-check" id="btncheck2" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck2">Checkbox 2</label>
+        <input type="checkbox" class="btn-check" id="btncheck3" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btncheck3">Checkbox 3</label>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Basic radio toggle button group">
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked>
+        <label class="btn btn-outline-primary" for="btnradio1">Radio 1</label>
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btnradio2">Radio 2</label>
+        <input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off">
+        <label class="btn btn-outline-primary" for="btnradio3">Radio 3</label>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Button toolbar
+
+Combine several button groups, and optionally input groups, inside a labeled toolbar.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column gap-3">
+    <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
+        <twig:ButtonGroup label="First group" class="me-2">
+            <twig:Button>1</twig:Button>
+            <twig:Button>2</twig:Button>
+            <twig:Button>3</twig:Button>
+            <twig:Button>4</twig:Button>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Second group" class="me-2">
+            <twig:Button color="secondary">5</twig:Button>
+            <twig:Button color="secondary">6</twig:Button>
+            <twig:Button color="secondary">7</twig:Button>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Third group">
+            <twig:Button color="info">8</twig:Button>
+        </twig:ButtonGroup>
+    </div>
+
+    <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with input group">
+        <twig:ButtonGroup label="First group" class="me-2">
+            <twig:Button color="secondary" outline>1</twig:Button>
+            <twig:Button color="secondary" outline>2</twig:Button>
+            <twig:Button color="secondary" outline>3</twig:Button>
+            <twig:Button color="secondary" outline>4</twig:Button>
+        </twig:ButtonGroup>
+        <div class="input-group">
+            <div class="input-group-text" id="btnGroupAddon">@</div>
+            <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon">
+        </div>
+    </div>
+
+    <div class="btn-toolbar justify-content-between" role="toolbar" aria-label="Toolbar with justified input group">
+        <twig:ButtonGroup label="First group">
+            <twig:Button color="secondary" outline>1</twig:Button>
+            <twig:Button color="secondary" outline>2</twig:Button>
+            <twig:Button color="secondary" outline>3</twig:Button>
+            <twig:Button color="secondary" outline>4</twig:Button>
+        </twig:ButtonGroup>
+        <div class="input-group">
+            <div class="input-group-text" id="btnGroupAddon2">@</div>
+            <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon2">
+        </div>
+    </div>
+</div>
+```
+
+### Sizing
+
+Apply a size to the group instead of repeating it on every button.
+
+```twig {"preview":true,"height":"230px"}
+<div class="d-flex flex-column align-items-start gap-2">
+    <twig:ButtonGroup size="lg" label="Large button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup label="Default button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup size="sm" label="Small button group">
+        <twig:Button outline>Left</twig:Button>
+        <twig:Button outline>Middle</twig:Button>
+        <twig:Button outline>Right</twig:Button>
+    </twig:ButtonGroup>
+</div>
+```
+
+### Nesting
+
+Nest a button group to place a dropdown alongside other buttons.
+
+```twig {"preview":true,"height":"260px"}
+<twig:ButtonGroup label="Button group with nested dropdown">
+    <twig:Button>1</twig:Button>
+    <twig:Button>2</twig:Button>
+    <twig:ButtonGroup label="Dropdown menu">
+        <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+        <ul class="dropdown-menu">
+            <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+        </ul>
+    </twig:ButtonGroup>
+</twig:ButtonGroup>
+```
+
+### Vertical variation
+
+Stack buttons, dropdowns, or radio controls in a vertical group.
+
+```twig {"preview":true,"height":"320px"}
+<div class="d-flex flex-wrap align-items-start gap-4">
+    <twig:ButtonGroup vertical label="Vertical button group">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup vertical label="Vertical button group with dropdowns">
+        <twig:ButtonGroup label="Dropdown menu">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+        <twig:ButtonGroup label="Dropstart menu" class="dropstart">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Dropend menu" class="dropend">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+        <twig:ButtonGroup label="Dropup menu" class="dropup">
+            <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+            <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+                <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+            </ul>
+        </twig:ButtonGroup>
+    </twig:ButtonGroup>
+
+    <twig:ButtonGroup vertical label="Vertical radio toggle button group">
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked>
+        <label class="btn btn-outline-danger" for="vbtn-radio1">Radio 1</label>
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off">
+        <label class="btn btn-outline-danger" for="vbtn-radio2">Radio 2</label>
+        <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio3" autocomplete="off">
+        <label class="btn btn-outline-danger" for="vbtn-radio3">Radio 3</label>
+    </twig:ButtonGroup>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button-group/manifest.json b/src/Toolkit/kits/bootstrap/button-group/manifest.json
new file mode 100644
index 00000000000..1bf2a3a363b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Button Group",
+    "description": "Groups a series of buttons on a single line or in a vertical column.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
new file mode 100644
index 00000000000..3bae952f3a3
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
@@ -0,0 +1,22 @@
+{# @prop vertical boolean Whether to stack the buttons vertically. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap size applied to every button in the group. #}
+{# @prop label string|null The accessible name for the group. #}
+{# @block content The buttons, links, or form controls contained in the group. #}
+{%- props
+    vertical = false,
+    size = null,
+    label = 'Button group'
+-%}
+
+{%- set classes = html_classes({
+    (vertical ? 'btn-group-vertical' : 'btn-group'): true,
+    ('btn-group-' ~ size): size in ['lg', 'sm'],
+}) -%}
+{%- set default_attrs = {class: classes, role: 'group'} -%}
+{%- if label not in [null, ''] -%}
+    {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+
+<div {{ attributes.defaults(default_attrs) }}>
+    {%- block content -%}{%- endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/button/README.md b/src/Toolkit/kits/bootstrap/button/README.md
new file mode 100644
index 00000000000..f2c5d177f7d
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/README.md
@@ -0,0 +1,197 @@
+# Button
+
+Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button>Primary</twig:Button>
+    <twig:Button color="secondary">Secondary</twig:Button>
+    <twig:Button color="success">Success</twig:Button>
+    <twig:Button color="danger">Danger</twig:Button>
+    <twig:Button color="warning">Warning</twig:Button>
+    <twig:Button color="info">Info</twig:Button>
+    <twig:Button color="light">Light</twig:Button>
+    <twig:Button color="dark">Dark</twig:Button>
+    <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Button>Save changes</twig:Button>
+```
+
+## Accessibility
+
+Do not rely on button color alone to communicate meaning. Use explicit labels or additional visually hidden text when needed.
+
+Disabled links omit their `href`, expose `aria-disabled="true"`, and are removed from keyboard navigation.
+
+## Examples
+
+### Base class
+
+Use the base Bootstrap button class without a contextual color when defining a custom style.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button :color="null">Base class</twig:Button>
+```
+
+### Variants
+
+Use contextual colors to communicate the purpose of an action.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button>Primary</twig:Button>
+    <twig:Button color="secondary">Secondary</twig:Button>
+    <twig:Button color="success">Success</twig:Button>
+    <twig:Button color="danger">Danger</twig:Button>
+    <twig:Button color="warning">Warning</twig:Button>
+    <twig:Button color="info">Info</twig:Button>
+    <twig:Button color="light">Light</twig:Button>
+    <twig:Button color="dark">Dark</twig:Button>
+    <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+### Disable text wrapping
+
+Add Bootstrap's `text-nowrap` utility when a button label must remain on one line.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button class="text-nowrap">This button label does not wrap</twig:Button>
+```
+
+### Button tags
+
+Render the component as a button, link, or input depending on the semantic element required.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button tag="a" href="#">Link</twig:Button>
+    <twig:Button type="submit">Button</twig:Button>
+    <twig:Button tag="input" value="Input" />
+    <twig:Button tag="input" type="submit" value="Submit" />
+    <twig:Button tag="input" type="reset" value="Reset" />
+</div>
+```
+
+### Outline buttons
+
+Use outline styles for actions that need less visual weight.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+    <twig:Button outline>Primary</twig:Button>
+    <twig:Button color="secondary" outline>Secondary</twig:Button>
+    <twig:Button color="success" outline>Success</twig:Button>
+    <twig:Button color="danger" outline>Danger</twig:Button>
+    <twig:Button color="warning" outline>Warning</twig:Button>
+    <twig:Button color="info" outline>Info</twig:Button>
+    <twig:Button color="light" outline>Light</twig:Button>
+    <twig:Button color="dark" outline>Dark</twig:Button>
+</div>
+```
+
+### Sizes
+
+Use Bootstrap's large and small sizes, or customize the component with Bootstrap CSS variables.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button size="lg">Large button</twig:Button>
+        <twig:Button color="secondary" size="lg">Large button</twig:Button>
+    </div>
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button size="sm">Small button</twig:Button>
+        <twig:Button color="secondary" size="sm">Small button</twig:Button>
+    </div>
+    <twig:Button style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">
+        Custom button
+    </twig:Button>
+</div>
+```
+
+### Disabled state
+
+Disable buttons and links while preserving the appropriate HTML and accessibility semantics.
+
+```twig {"preview":true,"height":"200px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button disabled>Primary button</twig:Button>
+        <twig:Button color="secondary" disabled>Button</twig:Button>
+        <twig:Button outline disabled>Primary button</twig:Button>
+        <twig:Button color="secondary" outline disabled>Button</twig:Button>
+    </div>
+    <div class="d-flex flex-wrap gap-2">
+        <twig:Button tag="a" disabled>Primary link</twig:Button>
+        <twig:Button tag="a" color="secondary" disabled>Link</twig:Button>
+    </div>
+</div>
+```
+
+### Block buttons
+
+Combine the component with Bootstrap layout utilities to create responsive full-width buttons.
+
+```twig {"preview":true,"height":"470px"}
+<div class="d-flex flex-column gap-4">
+    <div class="d-grid gap-2">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 d-md-block">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 col-6 mx-auto">
+        <twig:Button>Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+    <div class="d-grid gap-2 d-md-flex justify-content-md-end">
+        <twig:Button class="me-md-2">Button</twig:Button>
+        <twig:Button>Button</twig:Button>
+    </div>
+</div>
+```
+
+### Toggle states
+
+Use Bootstrap's button plugin for controls that toggle between pressed and unpressed states.
+
+```twig {"preview":true,"height":"300px"}
+<div class="d-flex flex-column align-items-start gap-3">
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button :color="null" data-bs-toggle="button">Toggle button</twig:Button>
+        <twig:Button :color="null" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+        <twig:Button :color="null" disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button data-bs-toggle="button">Toggle button</twig:Button>
+        <twig:Button class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+        <twig:Button disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button tag="a" :color="null" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+        <twig:Button tag="a" :color="null" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+        <twig:Button tag="a" :color="null" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+    </div>
+    <div class="d-inline-flex flex-wrap gap-1">
+        <twig:Button tag="a" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+        <twig:Button tag="a" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+        <twig:Button tag="a" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+    </div>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button/manifest.json b/src/Toolkit/kits/bootstrap/button/manifest.json
new file mode 100644
index 00000000000..582b11f728e
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/manifest.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "../../../schema-kit-recipe-v1.json",
+    "type": "component",
+    "name": "Button",
+    "description": "Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.",
+    "version-added": "3.4",
+    "copy-files": {
+        "templates/": "templates/"
+    },
+    "dependencies": {
+        "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+    }
+}
diff --git a/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
new file mode 100644
index 00000000000..dfbdedd725b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
@@ -0,0 +1,64 @@
+{# @prop tag 'button'|'a'|'input' The HTML element to render. #}
+{# @prop type 'button'|'submit'|'reset'|null The type used for button and input elements. #}
+{# @prop href string|null The destination used for anchor elements. #}
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'link'|null The Bootstrap contextual color. #}
+{# @prop outline boolean Whether to use an outline style. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap button size. #}
+{# @prop disabled boolean Whether the control is disabled. #}
+{# @prop value string|null The value rendered by input elements. #}
+{# @prop content string The fallback label when no content block is provided. #}
+{# @block content The button label and optional inline content. #}
+{%- props
+    tag = 'button',
+    type = null,
+    href = null,
+    color = 'primary',
+    outline = false,
+    size = null,
+    disabled = false,
+    value = null,
+    content = ''
+-%}
+
+{%- set final_tag = tag|lower in ['a', 'input'] ? tag|lower : 'button' -%}
+{%- set final_type = type in ['button', 'submit', 'reset'] ? type : 'button' -%}
+{%- set valid_colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'link'] -%}
+{%- set classes = html_classes({
+    btn: true,
+    ('btn-' ~ (outline and color != 'link' ? 'outline-' : '') ~ color): color in valid_colors,
+    ('btn-' ~ size): size in ['lg', 'sm'],
+    disabled: disabled and final_tag == 'a',
+}) -%}
+{%- set default_attrs = {class: classes} -%}
+
+{%- if final_tag == 'a' -%}
+    {%- set default_attrs = default_attrs|merge({role: 'button'}) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({'aria-disabled': 'true', tabindex: '-1'}) -%}
+    {%- else -%}
+        {%- set default_attrs = default_attrs|merge({href: href not in [null, ''] ? href : '#'}) -%}
+    {%- endif -%}
+{%- elseif final_tag == 'input' -%}
+    {%- set default_attrs = default_attrs|merge({
+        type: final_type,
+        value: value ?? content,
+    }) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+    {%- endif -%}
+{%- else -%}
+    {%- set default_attrs = default_attrs|merge({type: final_type}) -%}
+    {%- if disabled -%}
+        {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+    {%- endif -%}
+{%- endif -%}
+
+{%- if final_tag == 'input' -%}
+    <input {{ attributes.defaults(default_attrs) }}>
+{%- else -%}
+    <{{ final_tag }} {{ attributes.defaults(default_attrs) }}>
+        {%- block content -%}
+            {{- content -}}
+        {%- endblock -%}
+    </{{ final_tag }}>
+{%- endif -%}
diff --git a/src/Toolkit/kits/bootstrap/card/README.md b/src/Toolkit/kits/bootstrap/card/README.md
new file mode 100644
index 00000000000..fbde5eab9b6
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/card/README.md
@@ -0,0 +1,357 @@
+# Card
+
+Build flexible content containers with optional headers, footers, images, and contextual styles.
+
+```twig {"preview":true,"height":"480px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 22rem;">
+    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+    <twig:Card:Body>
+        <twig:Card:Title>Explore the outdoors</twig:Card:Title>
+        <twig:Card:Subtitle class="mb-2 text-body-secondary">Weekend inspiration</twig:Card:Subtitle>
+        <twig:Card:Text>Discover a new trail and make time for a change of scenery.</twig:Card:Text>
+        <a href="#" class="btn btn-primary">Find a trail</a>
+        <twig:Card:Link href="#">View guide</twig:Card:Link>
+    </twig:Card:Body>
+    <twig:Card:Footer class="text-body-secondary">Updated today</twig:Card:Footer>
+</twig:Card>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Card style="max-width: 24rem;">
+    <twig:Card:Header>Featured</twig:Card:Header>
+    <twig:Card:Body>
+        <twig:Card:Title>Card title</twig:Card:Title>
+        <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+    </twig:Card:Body>
+    <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+</twig:Card>
+```
+
+## Accessibility
+
+Choose a semantic heading level for each card title that fits the surrounding page hierarchy.
+
+Do not rely on background, text, or border color alone to communicate meaning. Keep sufficient contrast and provide an equivalent text cue when color carries information.
+
+## Examples
+
+Combine an image, title, supporting text, and action in a fixed-width card.
+
+```twig {"preview":true,"height":"400px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 18rem;">
+    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+    <twig:Card:Body>
+        <twig:Card:Title>Card title</twig:Card:Title>
+        <twig:Card:Text>Some quick example text to build on the card title and make up the bulk of the card's content.</twig:Card:Text>
+        <a href="#" class="btn btn-primary">Go somewhere</a>
+    </twig:Card:Body>
+</twig:Card>
+```
+
+### Content types
+
+Mix card bodies, titles, subtitles, text, links, images, and list groups.
+
+```twig {"preview":true,"height":"470px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<div class="d-flex flex-wrap gap-3">
+    <twig:Card style="width: 18rem;">
+        <twig:Card:Body>
+            <twig:Card:Title>Card title</twig:Card:Title>
+            <twig:Card:Subtitle class="mb-2 text-body-secondary">Card subtitle</twig:Card:Subtitle>
+            <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+            <twig:Card:Link href="#">Card link</twig:Card:Link>
+            <twig:Card:Link href="#">Another link</twig:Card:Link>
+        </twig:Card:Body>
+    </twig:Card>
+
+    <twig:Card style="width: 18rem;">
+        <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+        <twig:Card:Body>
+            <twig:Card:Text>Text, images, list groups, and links can be freely combined.</twig:Card:Text>
+        </twig:Card:Body>
+        <ul class="list-group list-group-flush">
+            <li class="list-group-item">An item</li>
+            <li class="list-group-item">A second item</li>
+            <li class="list-group-item">A third item</li>
+        </ul>
+        <twig:Card:Body>
+            <twig:Card:Link href="#">Card link</twig:Card:Link>
+        </twig:Card:Body>
+    </twig:Card>
+</div>
+```
+
+### Header and footer
+
+Add optional headers and footers, including semantic heading and quote content.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-grid gap-3">
+    <twig:Card>
+        <twig:Card:Header>Featured</twig:Card:Header>
+        <twig:Card:Body>
+            <twig:Card:Title>Special title treatment</twig:Card:Title>
+            <twig:Card:Text>With supporting text below as a natural lead-in to additional content.</twig:Card:Text>
+            <a href="#" class="btn btn-primary">Go somewhere</a>
+        </twig:Card:Body>
+    </twig:Card>
+
+    <twig:Card class="text-center">
+        <twig:Card:Header tag="h5">Featured</twig:Card:Header>
+        <twig:Card:Body>
+            <figure class="mb-0">
+                <blockquote class="blockquote"><p>A well-known quote, contained in a blockquote element.</p></blockquote>
+                <figcaption class="blockquote-footer mb-0">Someone famous in <cite title="Source Title">Source Title</cite></figcaption>
+            </figure>
+        </twig:Card:Body>
+        <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+    </twig:Card>
+</div>
+```
+
+### Sizing
+
+Size cards with Bootstrap's grid, width utilities, or custom CSS.
+
+```twig {"preview":true,"height":"190px"}
+<div class="row">
+    <div class="col-sm-6 mb-3 mb-sm-0">
+        <twig:Card>
+            <twig:Card:Body>
+                <twig:Card:Title>Grid-sized card</twig:Card:Title>
+                <twig:Card:Text>The surrounding Bootstrap grid controls this card's width.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+    <div class="col-sm-6">
+        <twig:Card class="w-75">
+            <twig:Card:Body>
+                <twig:Card:Title>Utility-sized card</twig:Card:Title>
+                <twig:Card:Text>Width utilities or custom CSS can size a card directly.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+</div>
+```
+
+### Text alignment
+
+Apply Bootstrap text alignment utilities to a card or one of its sections.
+
+```twig {"preview":true,"height":"440px"}
+<div class="d-flex flex-wrap gap-3">
+    {% for alignment, label in {'': 'Start', 'text-center': 'Center', 'text-end': 'End'} %}
+        <twig:Card class="{{ alignment }}" style="width: 18rem;">
+            <twig:Card:Body>
+                <twig:Card:Title>{{ label }} aligned</twig:Card:Title>
+                <twig:Card:Text>Use Bootstrap text utilities on the whole card or an individual section.</twig:Card:Text>
+                <a href="#" class="btn btn-primary">Go somewhere</a>
+            </twig:Card:Body>
+        </twig:Card>
+    {% endfor %}
+</div>
+```
+
+### Navigation
+
+Place tabs or pills in the card header.
+
+```twig {"preview":true,"height":"350px"}
+<div class="d-grid gap-3">
+    {% for style in ['tabs', 'pills'] %}
+        <twig:Card class="text-center">
+            <twig:Card:Header>
+                <ul class="nav nav-{{ style }} card-header-{{ style }}">
+                    <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
+                    <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+                    <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+                </ul>
+            </twig:Card:Header>
+            <twig:Card:Body>
+                <twig:Card:Title>Special title treatment</twig:Card:Title>
+                <twig:Card:Text>Card headers can contain Bootstrap tab or pill navigation.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    {% endfor %}
+</div>
+```
+
+### Image caps
+
+Position an image at the top or bottom edge of a card.
+
+```twig {"preview":true,"height":"350px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=960&q=80' %}
+
+<div class="row row-cols-1 row-cols-sm-2 g-3">
+    <div class="col">
+        <twig:Card class="h-100">
+            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+            <twig:Card:Body>
+                <twig:Card:Title>Top image cap</twig:Card:Title>
+                <twig:Card:Text>Images can sit at either end of a card.</twig:Card:Text>
+            </twig:Card:Body>
+        </twig:Card>
+    </div>
+    <div class="col">
+        <twig:Card class="h-100">
+            <twig:Card:Body>
+                <twig:Card:Title>Bottom image cap</twig:Card:Title>
+                <twig:Card:Text>The bottom position matches the lower card corners.</twig:Card:Text>
+            </twig:Card:Body>
+            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" position="bottom" />
+        </twig:Card>
+    </div>
+</div>
+```
+
+### Image overlays
+
+Use an image as the card background and place concise, high-contrast content over it.
+
+```twig {"preview":true,"height":"410px"}
+{% set image_url = 'https://images.unsplash.com/p…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant